Improved message 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
26e3d92b 8use Carp qw[croak];
9use HTTP::Response qw[];
10use IO::Handle qw[];
6faa5a50 11use IO::File qw[SEEK_SET SEEK_END];
26e3d92b 12use Symbol qw[];
b2e1304d 13
26e3d92b 14__PACKAGE__->mk_accessors(qw[environment request is_restored is_setuped is_prepared should_dup should_restore should_rewind stdin stdout stderr]);
b2e1304d 15
26e3d92b 16our $VERSION = 0.6_01;
b2e1304d 17
18sub new {
26e3d92b 19 my $class = ref $_[0] ? ref shift : shift;
20 my $params = {};
4e0afe7d 21
26e3d92b 22 if ( @_ % 2 == 0 ) {
23 $params = { @_ };
24 }
25 else {
26 $params = { request => shift, environment => { @_ } };
27 }
28
29 return bless( {}, $class )->initialize($params);
30}
31
32sub initialize {
33 my ( $self, $params ) = @_;
34
35 if ( exists $params->{request} ) {
36 $self->request( $params->{request} );
37 }
38 else {
39 croak("Mandatory parameter 'request' is missing.");
2d51e42f 40 }
4e0afe7d 41
26e3d92b 42 if ( exists $params->{environment} ) {
43 $self->environment( $params->{environment} );
44 }
45 else {
46 $self->environment( {} );
47 }
48
49 if ( exists $params->{stdin} ) {
50 $self->stdin( $params->{stdin} );
51 }
52 else {
53 $self->stdin( IO::File->new_tmpfile );
54 }
55
56 if ( exists $params->{stdout} ) {
57 $self->stdout( $params->{stdout} );
58 }
59 else {
60 $self->stdout( IO::File->new_tmpfile );
61 }
62
63 if ( exists $params->{stderr} ) {
64 $self->stderr( $params->{stderr} );
65 }
4d6e304a 66
26e3d92b 67 if ( exists $params->{dup} ) {
68 $self->should_dup( $params->{dup} ? 1 : 0 );
69 }
70 else {
71 $self->should_dup(1);
4d6e304a 72 }
26e3d92b 73
74 if ( exists $params->{restore} ) {
75 $self->should_restore( $params->{restore} ? 1 : 0 );
76 }
77 else {
78 $self->should_restore(1);
79 }
80
81 if ( exists $params->{rewind} ) {
82 $self->should_rewind( $params->{rewind} ? 1 : 0 );
83 }
84 else {
85 $self->should_rewind(1);
86 }
87
88 $self->prepare;
89
90 return $self;
91}
92
93*enviroment = \&environment;
94
95sub has_stdin { return defined $_[0]->stdin }
96sub has_stdout { return defined $_[0]->stdout }
97sub has_stderr { return defined $_[0]->stderr }
98
99sub prepare {
100 my $self = shift;
101
102 my $environment = $self->environment;
103 my $request = $self->request;
b2e1304d 104
30efa07d 105 my $host = $request->header('Host');
106 my $uri = $request->uri->clone;
26e3d92b 107
30efa07d 108 $uri->scheme('http') unless $uri->scheme;
109 $uri->host('localhost') unless $uri->host;
110 $uri->port(80) unless $uri->port;
a3875fc6 111 $uri->host_port($host) unless !$host || ( $host eq $uri->host_port );
4e0afe7d 112
ca38286c 113 $uri = $uri->canonical;
30efa07d 114
26e3d92b 115 my %cgi = (
b2e1304d 116 GATEWAY_INTERFACE => 'CGI/1.1',
30efa07d 117 HTTP_HOST => $uri->host_port,
a3875fc6 118 HTTPS => ( $uri->scheme eq 'https' ) ? 'ON' : 'OFF', # not in RFC 3875
119 PATH_INFO => $uri->path,
120 QUERY_STRING => $uri->query || '',
121 SCRIPT_NAME => '/',
122 SERVER_NAME => $uri->host,
123 SERVER_PORT => $uri->port,
124 SERVER_PROTOCOL => $request->protocol || 'HTTP/1.1',
125 SERVER_SOFTWARE => "HTTP-Request-AsCGI/$VERSION",
126 REMOTE_ADDR => '127.0.0.1',
127 REMOTE_HOST => 'localhost',
128 REMOTE_PORT => int( rand(64000) + 1000 ), # not in RFC 3875
129 REQUEST_URI => $uri->path_query, # not in RFC 3875
26e3d92b 130 REQUEST_METHOD => $request->method
131 );
b2e1304d 132
26e3d92b 133 foreach my $key ( keys %cgi ) {
134
135 unless ( exists $environment->{ $key } ) {
136 $environment->{ $key } = $cgi{ $key };
137 }
138 }
139
140 foreach my $field ( $self->request->headers->header_field_names ) {
b2e1304d 141
ca38286c 142 my $key = uc("HTTP_$field");
2aaf55bc 143 $key =~ tr/-/_/;
ca38286c 144 $key =~ s/^HTTP_// if $field =~ /^Content-(Length|Type)$/;
b2e1304d 145
26e3d92b 146 unless ( exists $environment->{ $key } ) {
147 $environment->{ $key } = $self->request->headers->header($field);
b2e1304d 148 }
149 }
150
26e3d92b 151 unless ( $environment->{SCRIPT_NAME} eq '/' && $environment->{PATH_INFO} ) {
152 $environment->{PATH_INFO} =~ s/^\Q$environment->{SCRIPT_NAME}\E/\//;
153 $environment->{PATH_INFO} =~ s/^\/+/\//;
154 }
155
156 $self->is_prepared(1);
157}
158
159sub setup {
160 my $self = shift;
161
162 $self->setup_stdin;
163 $self->setup_stdout;
164 $self->setup_stderr;
165 $self->setup_environment;
166
167 if ( $INC{'CGI.pm'} ) {
168 CGI::initialize_globals();
ca38286c 169 }
170
26e3d92b 171 $self->is_setuped(1);
ca38286c 172
173 return $self;
b2e1304d 174}
175
26e3d92b 176sub setup_environment {
b2e1304d 177 my $self = shift;
178
26e3d92b 179 no warnings 'uninitialized';
180
181 if ( $self->should_restore ) {
182 $self->{restore}->{environment} = { %ENV };
183 }
184
185 %ENV = %{ $self->environment };
186}
187
188sub setup_stdin {
189 my $self = shift;
190
191 if ( $self->has_stdin ) {
192
193 binmode( $self->stdin );
194
195 if ( $self->request->content_length ) {
b2e1304d 196
26e3d92b 197 syswrite( $self->stdin, $self->request->content )
198 or croak("Couldn't write request content to stdin handle: '$!'");
b2e1304d 199
26e3d92b 200 sysseek( $self->stdin, 0, SEEK_SET )
201 or croak("Couldn't seek stdin handle: '$!'");
202 }
203
204 if ( $self->should_dup ) {
205
206 if ( $self->should_restore ) {
207
208 open( my $stdin, '<&STDIN' )
209 or croak("Couldn't dup STDIN: '$!'");
210
211 $self->{restore}->{stdin} = $stdin;
212 }
213
214 STDIN->fdopen( $self->stdin, '<' )
6faa5a50 215 or croak("Couldn't dup stdin handle to STDIN: '$!'");
26e3d92b 216 }
217 else {
218
219 my $stdin = Symbol::qualify_to_ref('STDIN');
220
221 if ( $self->should_restore ) {
222
223 $self->{restore}->{stdin} = *$stdin;
224 $self->{restore}->{stdin_ref} = \*$stdin;
225 }
b2e1304d 226
26e3d92b 227 *{ $stdin } = $self->stdin;
228 }
b2e1304d 229
26e3d92b 230 binmode( STDIN );
b2e1304d 231 }
26e3d92b 232}
233
234sub setup_stdout {
235 my $self = shift;
236
237 if ( $self->has_stdout ) {
b2e1304d 238
26e3d92b 239 if ( $self->should_dup ) {
ca38286c 240
26e3d92b 241 if ( $self->should_restore ) {
242
243 open( my $stdout, '>&STDOUT' )
244 or croak("Couldn't dup STDOUT: '$!'");
245
246 $self->{restore}->{stdout} = $stdout;
247 }
248
249 STDOUT->fdopen( $self->stdout, '>' )
6faa5a50 250 or croak("Couldn't dup stdout handle to STDOUT: '$!'");
26e3d92b 251 }
252 else {
30efa07d 253
26e3d92b 254 my $stdout = Symbol::qualify_to_ref('STDOUT');
090cc060 255
26e3d92b 256 if ( $self->should_restore ) {
441eeb04 257
26e3d92b 258 $self->{restore}->{stdout} = *$stdout;
259 $self->{restore}->{stdout_ref} = \*$stdout;
260 }
76391122 261
26e3d92b 262 *{ $stdout } = $self->stdout;
263 }
76391122 264
a3875fc6 265 binmode( $self->stdout );
266 binmode( STDOUT);
267 }
26e3d92b 268}
090cc060 269
26e3d92b 270sub setup_stderr {
271 my $self = shift;
30efa07d 272
26e3d92b 273 if ( $self->has_stderr ) {
090cc060 274
26e3d92b 275 if ( $self->should_dup ) {
441eeb04 276
26e3d92b 277 if ( $self->should_restore ) {
090cc060 278
26e3d92b 279 open( my $stderr, '>&STDERR' )
280 or croak("Couldn't dup STDERR: '$!'");
4e0afe7d 281
26e3d92b 282 $self->{restore}->{stderr} = $stderr;
283 }
b2e1304d 284
26e3d92b 285 STDERR->fdopen( $self->stderr, '>' )
6faa5a50 286 or croak("Couldn't dup stderr handle to STDERR: '$!'");
26e3d92b 287 }
288 else {
b2e1304d 289
26e3d92b 290 my $stderr = Symbol::qualify_to_ref('STDERR');
291
292 if ( $self->should_restore ) {
293
294 $self->{restore}->{stderr} = *$stderr;
295 $self->{restore}->{stderr_ref} = \*$stderr;
296 }
297
298 *{ $stderr } = $self->stderr;
299 }
300
301 binmode( $self->stderr );
302 binmode( STDERR );
303 }
b2e1304d 304}
305
780060e5 306sub response {
4d6e304a 307 my $self = shift;
308 my %params = ( headers_only => 0, sync => 0, @_ );
780060e5 309
6faa5a50 310 return undef unless $self->has_stdout;
780060e5 311
30efa07d 312 seek( $self->stdout, 0, SEEK_SET )
26e3d92b 313 or croak("Couldn't seek stdout handle: '$!'");
780060e5 314
6faa5a50 315 my $message = undef;
316 my $response = HTTP::Response->new( 200, 'OK' );
317 $response->protocol('HTTP/1.1');
318
decf17dc 319 while ( my $line = $self->stdout->getline ) {
6faa5a50 320 $message .= $line;
321 last if $message =~ /\x0d?\x0a\x0d?\x0a$/;
780060e5 322 }
26e3d92b 323
6faa5a50 324 if ( !$message ) {
325
326 $response->code(500);
327 $response->message('Internal Server Error');
328 $response->date( time );
329 $response->content( $response->error_as_HTML );
330 $response->content_type('text/html');
331 $response->content_length( length $response->content );
decf17dc 332
6faa5a50 333 return $response;
780060e5 334 }
335
6faa5a50 336 my $Token = qr/[\x21\x23-\x27\x2A\x2B\x2D\x2E\x30-\x39\x41-\x5A\x5E-\x7A\x7C\x7E]/;
337 my $Version = qr/HTTP\/[0-9]+\.[0-9]+/;
decf17dc 338
6faa5a50 339 if ( $message =~ s/^($Version)[\x09\x20]+(\d{3})[\x09\x20]+([\x20-\xFF]*)\x0D?\x0A//o ) {
decf17dc 340
6faa5a50 341 $response->protocol($1);
342 $response->code($2);
343 $response->message($3);
4e0afe7d 344 }
decf17dc 345
6faa5a50 346 $message =~ s/\x0D?\x0A[\x09\x20]+/\x20/gs;
decf17dc 347
6faa5a50 348 foreach ( split /\x0D?\x0A/, $message ) {
780060e5 349
6faa5a50 350 if ( /^($Token+)[\x09\x20]*:[\x09\x20]*([\x20-\xFF]+)[\x09\x20]*$/o ) {
351 $response->headers->push_header( $1 => $2 );
352 }
353 else {
354 # XXX what should we do on bad headers?
355 }
780060e5 356 }
26e3d92b 357
6faa5a50 358 my $status = $response->header('Status');
780060e5 359
6faa5a50 360 if ( $status && $status =~ /^(\d{3})[\x09\x20]+([\x20-\xFF]+)$/ ) {
361 $response->code($1);
362 $response->message($2);
363 }
4e0afe7d 364
6faa5a50 365 if ( !$response->date ) {
366 $response->date(time);
4e0afe7d 367 }
780060e5 368
4d6e304a 369 if ( $params{headers_only} ) {
4e0afe7d 370
4d6e304a 371 if ( $params{sync} ) {
4e0afe7d 372
4d6e304a 373 my $position = tell( $self->stdout )
374 or croak("Couldn't get file position from stdout handle: '$!'");
4e0afe7d 375
4d6e304a 376 sysseek( $self->stdout, $position, SEEK_SET )
377 or croak("Couldn't seek stdout handle: '$!'");
378 }
4e0afe7d 379
4d6e304a 380 return $response;
780060e5 381 }
4e0afe7d 382
4d6e304a 383 my $content = undef;
384 my $content_length = 0;
385
386 while () {
387
6faa5a50 388 my $r = $self->stdout->read( $content, 65536, $content_length );
4e0afe7d 389
4d6e304a 390 if ( defined $r ) {
391
392 $content_length += $r;
393
394 last unless $r;
395 }
396 else {
6faa5a50 397 croak("Couldn't read response content from stdin handle: '$!'");
780060e5 398 }
4d6e304a 399 }
400
401 if ( $content_length ) {
402
403 $response->content_ref(\$content);
4e0afe7d 404
4d6e304a 405 if ( !$response->content_length ) {
406 $response->content_length($content_length);
decf17dc 407 }
780060e5 408 }
409
780060e5 410 return $response;
411}
412
b2e1304d 413sub restore {
414 my $self = shift;
4e0afe7d 415
26e3d92b 416 if ( $self->should_restore ) {
417
418 $self->restore_environment;
419 $self->restore_stdin;
420 $self->restore_stdout;
421 $self->restore_stderr;
422
423 $self->{restore} = {};
424
425 $self->is_restored(1);
cef1c068 426 }
b2e1304d 427
26e3d92b 428 return $self;
429}
430
431sub restore_environment {
432 my $self = shift;
433
434 no warnings 'uninitialized';
b2e1304d 435
26e3d92b 436 %ENV = %{ $self->{restore}->{environment} };
437}
438
439sub restore_stdin {
440 my $self = shift;
12852959 441
26e3d92b 442 if ( $self->has_stdin ) {
30efa07d 443
26e3d92b 444 my $stdin = $self->{restore}->{stdin};
30efa07d 445
26e3d92b 446 if ( $self->should_dup ) {
447
448 STDIN->fdopen( $stdin, '<' )
449 or croak("Couldn't restore STDIN: '$!'");
450 }
451 else {
452
453 my $stdin_ref = $self->{restore}->{stdin_ref};
454
455 *{ $stdin_ref } = $stdin;
456 }
090cc060 457
26e3d92b 458 if ( $self->should_rewind ) {
459
460 seek( $self->stdin, 0, SEEK_SET )
461 or croak("Couldn't seek stdin handle: '$!'");
462 }
a3875fc6 463 }
26e3d92b 464}
465
466sub restore_stdout {
467 my $self = shift;
468
469 if ( $self->has_stdout ) {
470
471 my $stdout = $self->{restore}->{stdout};
472
473 if ( $self->should_dup ) {
474
475 STDOUT->flush
476 or croak("Couldn't flush STDOUT: '$!'");
477
478 STDOUT->fdopen( $stdout, '>' )
479 or croak("Couldn't restore STDOUT: '$!'");
480 }
481 else {
12852959 482
26e3d92b 483 my $stdout_ref = $self->{restore}->{stdout_ref};
30efa07d 484
26e3d92b 485 *{ $stdout_ref } = $stdout;
486 }
30efa07d 487
26e3d92b 488 if ( $self->should_rewind ) {
090cc060 489
26e3d92b 490 seek( $self->stdout, 0, SEEK_SET )
491 or croak("Couldn't seek stdout handle: '$!'");
492 }
6f5fb9a7 493 }
26e3d92b 494}
12852959 495
26e3d92b 496sub restore_stderr {
497 my $self = shift;
090cc060 498
26e3d92b 499 if ( $self->has_stderr ) {
500
501 my $stderr = $self->{restore}->{stderr};
502
503 if ( $self->should_dup ) {
504
505 STDERR->flush
506 or croak("Couldn't flush STDERR: '$!'");
507
508 STDERR->fdopen( $stderr, '>' )
509 or croak("Couldn't restore STDERR: '$!'");
510 }
511 else {
512
513 my $stderr_ref = $self->{restore}->{stderr_ref};
514
515 *{ $stderr_ref } = $stderr;
516 }
517
518 if ( $self->should_rewind ) {
519
520 seek( $self->stderr, 0, SEEK_SET )
521 or croak("Couldn't seek stderr handle: '$!'");
522 }
523 }
b2e1304d 524}
525
526sub DESTROY {
527 my $self = shift;
26e3d92b 528
6faa5a50 529 if ( $self->should_restore && $self->is_setuped && !$self->is_restored ) {
530 $self->restore;
26e3d92b 531 }
b2e1304d 532}
533
5341;
535
536__END__
537
538=head1 NAME
539
26e3d92b 540HTTP::Request::AsCGI - Setup a CGI environment from a HTTP::Request
b2e1304d 541
542=head1 SYNOPSIS
543
bd7813ac 544 use CGI;
545 use HTTP::Request;
546 use HTTP::Request::AsCGI;
26e3d92b 547
bd7813ac 548 my $request = HTTP::Request->new( GET => 'http://www.host.com/' );
549 my $stdout;
26e3d92b 550
bd7813ac 551 {
552 my $c = HTTP::Request::AsCGI->new($request)->setup;
553 my $q = CGI->new;
26e3d92b 554
bd7813ac 555 print $q->header,
556 $q->start_html('Hello World'),
557 $q->h1('Hello World'),
558 $q->end_html;
26e3d92b 559
bd7813ac 560 $stdout = $c->stdout;
26e3d92b 561
562 # environment and descriptors will automatically be restored
2d51e42f 563 # when $c is destructed.
bd7813ac 564 }
26e3d92b 565
bd7813ac 566 while ( my $line = $stdout->getline ) {
567 print $line;
568 }
26e3d92b 569
b2e1304d 570=head1 DESCRIPTION
571
26e3d92b 572Provides a convinient way of setting up an CGI environment from a HTTP::Request.
2d51e42f 573
b2e1304d 574=head1 METHODS
575
26e3d92b 576=over 4
b2e1304d 577
2d51e42f 578=item new ( $request [, key => value ] )
579
580Contructor, first argument must be a instance of HTTP::Request
ca38286c 581followed by optional pairs of environment key and value.
b2e1304d 582
26e3d92b 583=item environment
bd7813ac 584
26e3d92b 585Returns a hashref containing the environment that will be used in setup.
2d51e42f 586Changing the hashref after setup has been called will have no effect.
587
b2e1304d 588=item setup
589
2d51e42f 590Setups the environment and descriptors.
591
b2e1304d 592=item restore
593
26e3d92b 594Restores the environment and descriptors. Can only be called after setup.
2d51e42f 595
b2e1304d 596=item request
597
2d51e42f 598Returns the request given to constructor.
599
780060e5 600=item response
601
2d51e42f 602Returns a HTTP::Response. Can only be called after restore.
603
b2e1304d 604=item stdin
605
2d51e42f 606Accessor for handle that will be used for STDIN, must be a real seekable
607handle with an file descriptor. Defaults to a tempoary IO::File instance.
608
b2e1304d 609=item stdout
610
2d51e42f 611Accessor for handle that will be used for STDOUT, must be a real seekable
612handle with an file descriptor. Defaults to a tempoary IO::File instance.
613
b2e1304d 614=item stderr
615
2d51e42f 616Accessor for handle that will be used for STDERR, must be a real seekable
617handle with an file descriptor.
b2e1304d 618
2d51e42f 619=back
b2e1304d 620
74fbb9dd 621=head1 SEE ALSO
622
623=over 4
624
625=item examples directory in this distribution.
626
627=item L<WWW::Mechanize::CGI>
628
629=item L<Test::WWW::Mechanize::CGI>
630
631=back
632
2d51e42f 633=head1 THANKS TO
17b370b0 634
635Thomas L. Shinnick for his valuable win32 testing.
636
b2e1304d 637=head1 AUTHOR
638
639Christian Hansen, C<ch@ngmedia.com>
640
641=head1 LICENSE
642
26e3d92b 643This library is free software. You can redistribute it and/or modify
b2e1304d 644it under the same terms as perl itself.
645
646=cut