improved response header parsing
[catagits/HTTP-Request-AsCGI.git] / lib / HTTP / Request / AsCGI.pm
CommitLineData
b2e1304d 1package HTTP::Request::AsCGI;
2
3use strict;
4use warnings;
090cc060 5use bytes;
b2e1304d 6use base 'Class::Accessor::Fast';
7
8use Carp;
30efa07d 9use IO::Handle;
bd7813ac 10use IO::File;
b2e1304d 11
090cc060 12__PACKAGE__->mk_accessors(qw[ enviroment request stdin stdout stderr ]);
b2e1304d 13
14our $VERSION = 0.1;
15
16sub new {
17 my $class = shift;
18 my $request = shift;
19
20 my $self = {
21 request => $request,
22 restored => 0,
6f5fb9a7 23 setuped => 0,
bd7813ac 24 stdin => IO::File->new_tmpfile,
17b370b0 25 stdout => IO::File->new_tmpfile
b2e1304d 26 };
27
30efa07d 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
b2e1304d 35 $self->{enviroment} = {
36 GATEWAY_INTERFACE => 'CGI/1.1',
30efa07d 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 || '',
c1e07bf1 41 SCRIPT_NAME => '/',
30efa07d 42 SERVER_NAME => $uri->host,
43 SERVER_PORT => $uri->port,
b2e1304d 44 SERVER_PROTOCOL => $request->protocol || 'HTTP/1.1',
30efa07d 45 SERVER_SOFTWARE => "HTTP-Request-AsCGI/$VERSION",
b2e1304d 46 REMOTE_ADDR => '127.0.0.1',
47 REMOTE_HOST => 'localhost',
30efa07d 48 REMOTE_PORT => int( rand(64000) + 1000 ), # not in RFC 3875
49 REQUEST_URI => $uri->path_query || '/', # not in RFC 3875
b2e1304d 50 REQUEST_METHOD => $request->method,
51 @_
52 };
53
54 foreach my $field ( $request->headers->header_field_names ) {
55
56 my $key = uc($field);
2aaf55bc 57 $key =~ tr/-/_/;
b2e1304d 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
68sub setup {
69 my $self = shift;
70
090cc060 71 $self->{restore}->{enviroment} = {%ENV};
b2e1304d 72
090cc060 73 open( $self->{restore}->{stdin}, '>&', STDIN->fileno )
74 or croak("Can't dup stdin: $!");
b2e1304d 75
090cc060 76 open( STDIN, '<&=', $self->stdin->fileno )
77 or croak("Can't open stdin: $!");
441eeb04 78
17b370b0 79 binmode( $self->stdin );
80 binmode( STDIN );
b2e1304d 81
82 if ( $self->request->content_length ) {
83
30efa07d 84 syswrite( $self->stdin, $self->request->content )
780060e5 85 or croak("Can't write request content to stdin handle: $!");
b2e1304d 86
30efa07d 87 sysseek( $self->stdin, 0, SEEK_SET )
780060e5 88 or croak("Can't seek stdin handle: $!");
b2e1304d 89 }
90
090cc060 91 if ( $self->stdout ) {
30efa07d 92
090cc060 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: $!");
441eeb04 98
17b370b0 99 binmode( $self->stdout );
100 binmode( STDOUT);
090cc060 101 }
102
103 if ( $self->stderr ) {
30efa07d 104
090cc060 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: $!");
441eeb04 110
17b370b0 111 binmode( $self->stderr );
112 binmode( STDERR );
090cc060 113 }
114
3cdea3c7 115 {
116 no warnings 'uninitialized';
117 %ENV = %{ $self->enviroment };
118 }
30efa07d 119
120 if ( $INC{'CGI.pm'} ) {
121 CGI::initialize_globals();
122 }
b2e1304d 123
6f5fb9a7 124 $self->{setuped}++;
b2e1304d 125
126 return $self;
127}
128
780060e5 129sub response {
130 my ( $self, $callback ) = @_;
131
132 return undef unless $self->{setuped};
133 return undef unless $self->{restored};
090cc060 134 return undef unless $self->{restore}->{stdout};
780060e5 135
136 require HTTP::Response;
137
30efa07d 138 seek( $self->stdout, 0, SEEK_SET )
139 or croak("Can't seek stdout handle: $!");
780060e5 140
decf17dc 141 my $message;
142 while ( my $line = $self->stdout->getline ) {
780060e5 143 $message .= $line;
decf17dc 144 last if $message =~ /\x0d?\x0a\x0d?\x0a$/;
780060e5 145 }
146
147 unless ( $message =~ /^HTTP/ ) {
decf17dc 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'" );
780060e5 157 }
158
decf17dc 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 }
780060e5 173
174 if ( my $code = $response->header('Status') ) {
175 $response->code($code);
decf17dc 176 $response->message( HTTP::Status::status_message($code) );
780060e5 177 }
178
780060e5 179 $response->headers->date( time() );
180
090cc060 181 if ($callback) {
780060e5 182 $response->content( sub {
183 if ( $self->stdout->read( my $buffer, 4096 ) ) {
184 return $buffer;
185 }
186 return undef;
090cc060 187 });
780060e5 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 }
decf17dc 195
196 if ( $length && !$response->content_length ) {
197 $response->content_length($length);
198 }
780060e5 199 }
200
780060e5 201 return $response;
202}
203
b2e1304d 204sub 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
30efa07d 212 sysseek( $self->stdin, 0, SEEK_SET )
780060e5 213 or croak("Can't seek stdin: $!");
12852959 214
090cc060 215 if ( $self->{restore}->{stdout} ) {
30efa07d 216
217 STDOUT->flush
218 or croak("Can't flush stdout: $!");
219
090cc060 220 open( STDOUT, '>&', $self->{restore}->{stdout} )
221 or croak("Can't restore stdout: $!");
222
30efa07d 223 sysseek( $self->stdout, 0, SEEK_SET )
6f5fb9a7 224 or croak("Can't seek stdout: $!");
225 }
12852959 226
090cc060 227 if ( $self->{restore}->{stderr} ) {
30efa07d 228
229 STDERR->flush
230 or croak("Can't flush stderr: $!");
231
090cc060 232 open( STDERR, '>&', $self->{restore}->{stderr} )
233 or croak("Can't restore stderr: $!");
234
30efa07d 235 sysseek( $self->stderr, 0, SEEK_SET )
6f5fb9a7 236 or croak("Can't seek stderr: $!");
237 }
12852959 238
b2e1304d 239 $self->{restored}++;
090cc060 240
241 return $self;
b2e1304d 242}
243
244sub DESTROY {
245 my $self = shift;
6f5fb9a7 246 $self->restore if $self->{setuped} && !$self->{restored};
b2e1304d 247}
248
2491;
250
251__END__
252
253=head1 NAME
254
bd7813ac 255HTTP::Request::AsCGI - Setup a CGI enviroment from a HTTP::Request
b2e1304d 256
257=head1 SYNOPSIS
258
bd7813ac 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
bd7813ac 280 while ( my $line = $stdout->getline ) {
281 print $line;
282 }
283
b2e1304d 284=head1 DESCRIPTION
285
286=head1 METHODS
287
288=over 4
289
290=item new
291
bd7813ac 292=item enviroment
293
b2e1304d 294=item setup
295
296=item restore
297
298=item request
299
780060e5 300=item response
301
b2e1304d 302=item stdin
303
304=item stdout
305
306=item stderr
307
308=back
309
310=head1 BUGS
311
17b370b0 312=item THANKS TO
313
314Thomas L. Shinnick for his valuable win32 testing.
315
b2e1304d 316=head1 AUTHOR
317
318Christian Hansen, C<ch@ngmedia.com>
319
320=head1 LICENSE
321
322This library is free software. You can redistribute it and/or modify
323it under the same terms as perl itself.
324
325=cut