improved response header parsing
[catagits/HTTP-Request-AsCGI.git] / lib / HTTP / Request / AsCGI.pm
1 package HTTP::Request::AsCGI;
2
3 use strict;
4 use warnings;
5 use bytes;
6 use base 'Class::Accessor::Fast';
7
8 use Carp;
9 use IO::Handle;
10 use IO::File;
11
12 __PACKAGE__->mk_accessors(qw[ enviroment request stdin stdout stderr ]);
13
14 our $VERSION = 0.1;
15
16 sub new {
17     my $class   = shift;
18     my $request = shift;
19
20     my $self = {
21         request  => $request,
22         restored => 0,
23         setuped  => 0,
24         stdin    => IO::File->new_tmpfile,
25         stdout   => IO::File->new_tmpfile
26     };
27
28     my $host = $request->header('Host');
29     my $uri  = $request->uri->clone;
30     $uri->scheme('http')    unless $uri->scheme;
31     $uri->host('localhost') unless $uri->host;
32     $uri->port(80)          unless $uri->port;
33     $uri->host_port($host)  unless !$host || ( $host eq $uri->host_port );
34
35     $self->{enviroment} = {
36         GATEWAY_INTERFACE => 'CGI/1.1',
37         HTTP_HOST         => $uri->host_port,
38         HTTPS             => ( $uri->scheme eq 'https' ) ? 'ON' : 'OFF',  # not in RFC 3875
39         PATH_INFO         => $uri->path,
40         QUERY_STRING      => $uri->query || '',
41         SCRIPT_NAME       => '/',
42         SERVER_NAME       => $uri->host,
43         SERVER_PORT       => $uri->port,
44         SERVER_PROTOCOL   => $request->protocol || 'HTTP/1.1',
45         SERVER_SOFTWARE   => "HTTP-Request-AsCGI/$VERSION",
46         REMOTE_ADDR       => '127.0.0.1',
47         REMOTE_HOST       => 'localhost',
48         REMOTE_PORT       => int( rand(64000) + 1000 ),                   # not in RFC 3875
49         REQUEST_URI       => $uri->path_query || '/',                     # not in RFC 3875
50         REQUEST_METHOD    => $request->method,
51         @_
52     };
53
54     foreach my $field ( $request->headers->header_field_names ) {
55
56         my $key = uc($field);
57         $key =~ tr/-/_/;
58         $key = 'HTTP_' . $key unless $field =~ /^Content-(Length|Type)$/;
59
60         unless ( exists $self->{enviroment}->{$key} ) {
61             $self->{enviroment}->{$key} = $request->headers->header($field);
62         }
63     }
64
65     return $class->SUPER::new($self);
66 }
67
68 sub setup {
69     my $self = shift;
70
71     $self->{restore}->{enviroment} = {%ENV};
72
73     open( $self->{restore}->{stdin}, '>&', STDIN->fileno )
74       or croak("Can't dup stdin: $!");
75
76     open( STDIN, '<&=', $self->stdin->fileno )
77       or croak("Can't open stdin: $!");
78
79     binmode( $self->stdin );
80     binmode( STDIN );
81
82     if ( $self->request->content_length ) {
83
84         syswrite( $self->stdin, $self->request->content )
85           or croak("Can't write request content to stdin handle: $!");
86
87         sysseek( $self->stdin, 0, SEEK_SET )
88           or croak("Can't seek stdin handle: $!");
89     }
90
91     if ( $self->stdout ) {
92
93         open( $self->{restore}->{stdout}, '>&', STDOUT->fileno )
94           or croak("Can't dup stdout: $!");
95
96         open( STDOUT, '>&=', $self->stdout->fileno )
97           or croak("Can't open stdout: $!");
98
99         binmode( $self->stdout );
100         binmode( STDOUT);
101     }
102
103     if ( $self->stderr ) {
104
105         open( $self->{restore}->{stderr}, '>&', STDERR->fileno )
106           or croak("Can't dup stderr: $!");
107
108         open( STDERR, '>&=', $self->stderr->fileno )
109           or croak("Can't open stderr: $!");
110
111         binmode( $self->stderr );
112         binmode( STDERR );
113     }
114
115     {
116         no warnings 'uninitialized';
117         %ENV = %{ $self->enviroment };
118     }
119     
120     if ( $INC{'CGI.pm'} ) {
121         CGI::initialize_globals();
122     }    
123
124     $self->{setuped}++;
125
126     return $self;
127 }
128
129 sub response {
130     my ( $self, $callback ) = @_;
131
132     return undef unless $self->{setuped};
133     return undef unless $self->{restored};
134     return undef unless $self->{restore}->{stdout};
135
136     require HTTP::Response;
137
138     seek( $self->stdout, 0, SEEK_SET )
139       or croak("Can't seek stdout handle: $!");
140
141     my $message;
142     while ( my $line = $self->stdout->getline ) {
143         $message .= $line;
144         last if $message =~ /\x0d?\x0a\x0d?\x0a$/;
145     }
146
147     unless ( $message =~ /^HTTP/ ) {
148         $message = "HTTP/1.1 200 OK\x0d\x0a" . $message;
149     }
150
151     my $response = HTTP::Response->new;
152     my @headers  = split( /\x0d?\x0a/, $message );
153     my $status   = shift(@headers);
154
155     unless ( $status =~ s/^(HTTP\/\d\.\d) (\d{3}) (.*)$// ) {
156         croak( "Invalid Status-Line: '$status'" );
157     }
158
159     $response->protocol($1);
160     $response->code($2);
161     $response->message($3);
162
163     my $token = qr/[^][\x00-\x1f\x7f()<>@,;:\\"\/?={} \t]+/;
164
165     foreach my $header (@headers) {
166
167         unless( $header =~ s/^($token):[\t ]*// ) {
168             croak( "Invalid header field name : '$header'" );
169         }
170
171         $response->push_header( $1 => $header );
172     }    
173
174     if ( my $code = $response->header('Status') ) {
175         $response->code($code);
176         $response->message( HTTP::Status::status_message($code) );
177     }
178
179     $response->headers->date( time() );
180
181     if ($callback) {
182         $response->content( sub {
183             if ( $self->stdout->read( my $buffer, 4096 ) ) {
184                 return $buffer;
185             }
186             return undef;
187         });
188     }
189     else {
190         my $length = 0;
191         while ( $self->stdout->read( my $buffer, 4096 ) ) {
192             $length += length($buffer);
193             $response->add_content($buffer);
194         }
195         
196         if ( $length && !$response->content_length ) {
197             $response->content_length($length);
198         }
199     }
200
201     return $response;
202 }
203
204 sub restore {
205     my $self = shift;
206
207     %ENV = %{ $self->{restore}->{enviroment} };
208
209     open( STDIN, '>&', $self->{restore}->{stdin} )
210       or croak("Can't restore stdin: $!");
211
212     sysseek( $self->stdin, 0, SEEK_SET )
213       or croak("Can't seek stdin: $!");
214
215     if ( $self->{restore}->{stdout} ) {
216
217         STDOUT->flush
218           or croak("Can't flush stdout: $!");
219
220         open( STDOUT, '>&', $self->{restore}->{stdout} )
221           or croak("Can't restore stdout: $!");
222
223         sysseek( $self->stdout, 0, SEEK_SET )
224           or croak("Can't seek stdout: $!");
225     }
226
227     if ( $self->{restore}->{stderr} ) {
228
229         STDERR->flush
230           or croak("Can't flush stderr: $!");
231
232         open( STDERR, '>&', $self->{restore}->{stderr} )
233           or croak("Can't restore stderr: $!");
234
235         sysseek( $self->stderr, 0, SEEK_SET )
236           or croak("Can't seek stderr: $!");
237     }
238
239     $self->{restored}++;
240
241     return $self;
242 }
243
244 sub DESTROY {
245     my $self = shift;
246     $self->restore if $self->{setuped} && !$self->{restored};
247 }
248
249 1;
250
251 __END__
252
253 =head1 NAME
254
255 HTTP::Request::AsCGI - Setup a CGI enviroment from a HTTP::Request
256
257 =head1 SYNOPSIS
258
259     use CGI;
260     use HTTP::Request;
261     use HTTP::Request::AsCGI;
262     
263     my $request = HTTP::Request->new( GET => 'http://www.host.com/' );
264     my $stdout;
265     
266     {
267         my $c = HTTP::Request::AsCGI->new($request)->setup;
268         my $q = CGI->new;
269         
270         print $q->header,
271               $q->start_html('Hello World'),
272               $q->h1('Hello World'),
273               $q->end_html;
274         
275         $stdout = $c->stdout;
276         
277         # enviroment and descriptors will automatically be restored when $c is destructed.
278     }
279     
280     while ( my $line = $stdout->getline ) {
281         print $line;
282     }
283     
284 =head1 DESCRIPTION
285
286 =head1 METHODS
287
288 =over 4 
289
290 =item new
291
292 =item enviroment
293
294 =item setup
295
296 =item restore
297
298 =item request
299
300 =item response
301
302 =item stdin
303
304 =item stdout
305
306 =item stderr
307
308 =back
309
310 =head1 BUGS
311
312 =item THANKS TO
313
314 Thomas L. Shinnick for his valuable win32 testing.
315
316 =head1 AUTHOR
317
318 Christian Hansen, C<ch@ngmedia.com>
319
320 =head1 LICENSE
321
322 This library is free software. You can redistribute it and/or modify 
323 it under the same terms as perl itself.
324
325 =cut