Improved message 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            qw[croak];
9 use HTTP::Response  qw[];
10 use IO::Handle      qw[];
11 use IO::File        qw[SEEK_SET SEEK_END];
12 use Symbol          qw[];
13
14 __PACKAGE__->mk_accessors(qw[environment request is_restored is_setuped is_prepared should_dup should_restore should_rewind stdin stdout stderr]);
15
16 our $VERSION = 0.6_01;
17
18 sub new {
19     my $class  = ref $_[0] ? ref shift : shift;
20     my $params = {};
21
22     if ( @_ % 2 == 0 ) {
23         $params = { @_ };
24     }
25     else {
26         $params = { request => shift, environment => { @_ } };
27     }
28
29     return bless( {}, $class )->initialize($params);
30 }
31
32 sub 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.");
40     }
41
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     }
66
67     if ( exists $params->{dup} ) {
68         $self->should_dup( $params->{dup} ? 1 : 0 );
69     }
70     else {
71         $self->should_dup(1);
72     }
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
95 sub has_stdin  { return defined $_[0]->stdin  }
96 sub has_stdout { return defined $_[0]->stdout }
97 sub has_stderr { return defined $_[0]->stderr }
98
99 sub prepare {
100     my $self = shift;
101
102     my $environment = $self->environment;
103     my $request     = $self->request;
104
105     my $host = $request->header('Host');
106     my $uri  = $request->uri->clone;
107
108     $uri->scheme('http')    unless $uri->scheme;
109     $uri->host('localhost') unless $uri->host;
110     $uri->port(80)          unless $uri->port;
111     $uri->host_port($host)  unless !$host || ( $host eq $uri->host_port );
112
113     $uri = $uri->canonical;
114
115     my %cgi = (
116         GATEWAY_INTERFACE => 'CGI/1.1',
117         HTTP_HOST         => $uri->host_port,
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
130         REQUEST_METHOD    => $request->method
131     );
132
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 ) {
141
142         my $key = uc("HTTP_$field");
143         $key =~ tr/-/_/;
144         $key =~ s/^HTTP_// if $field =~ /^Content-(Length|Type)$/;
145
146         unless ( exists $environment->{ $key } ) {
147             $environment->{ $key } = $self->request->headers->header($field);
148         }
149     }
150
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
159 sub 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();
169     }
170
171     $self->is_setuped(1);
172
173     return $self;
174 }
175
176 sub setup_environment {
177     my $self = shift;
178
179     no warnings 'uninitialized';
180
181     if ( $self->should_restore ) {
182         $self->{restore}->{environment} = { %ENV };
183     }
184
185     %ENV = %{ $self->environment };
186 }
187
188 sub setup_stdin {
189     my $self = shift;
190
191     if ( $self->has_stdin ) {
192
193         binmode( $self->stdin );
194
195         if ( $self->request->content_length ) {
196
197             syswrite( $self->stdin, $self->request->content )
198               or croak("Couldn't write request content to stdin handle: '$!'");
199
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, '<' )
215               or croak("Couldn't dup stdin handle to STDIN: '$!'");
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             }
226
227             *{ $stdin } = $self->stdin;
228         }
229
230         binmode( STDIN );
231     }
232 }
233
234 sub setup_stdout {
235     my $self = shift;
236
237     if ( $self->has_stdout ) {
238
239         if ( $self->should_dup ) {
240
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, '>' )
250               or croak("Couldn't dup stdout handle to STDOUT: '$!'");
251         }
252         else {
253
254             my $stdout = Symbol::qualify_to_ref('STDOUT');
255
256             if ( $self->should_restore ) {
257
258                 $self->{restore}->{stdout}     = *$stdout;
259                 $self->{restore}->{stdout_ref} = \*$stdout;
260             }
261
262             *{ $stdout } = $self->stdout;
263         }
264
265         binmode( $self->stdout );
266         binmode( STDOUT);
267     }
268 }
269
270 sub setup_stderr {
271     my $self = shift;
272
273     if ( $self->has_stderr ) {
274
275         if ( $self->should_dup ) {
276
277             if ( $self->should_restore ) {
278
279                 open( my $stderr, '>&STDERR' )
280                   or croak("Couldn't dup STDERR: '$!'");
281
282                 $self->{restore}->{stderr} = $stderr;
283             }
284
285             STDERR->fdopen( $self->stderr, '>' )
286               or croak("Couldn't dup stderr handle to STDERR: '$!'");
287         }
288         else {
289
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     }
304 }
305
306 sub response {
307     my $self   = shift;
308     my %params = ( headers_only => 0, sync => 0, @_ );
309
310     return undef unless $self->has_stdout;
311
312     seek( $self->stdout, 0, SEEK_SET )
313       or croak("Couldn't seek stdout handle: '$!'");
314
315     my $message  = undef;
316     my $response = HTTP::Response->new( 200, 'OK' );
317        $response->protocol('HTTP/1.1');
318
319     while ( my $line = $self->stdout->getline ) {
320         $message .= $line;
321         last if $message =~ /\x0d?\x0a\x0d?\x0a$/;
322     }
323
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 );
332
333         return $response;
334     }
335
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]+/;
338
339     if ( $message =~ s/^($Version)[\x09\x20]+(\d{3})[\x09\x20]+([\x20-\xFF]*)\x0D?\x0A//o ) {
340
341         $response->protocol($1);
342         $response->code($2);
343         $response->message($3);
344     }
345
346     $message =~ s/\x0D?\x0A[\x09\x20]+/\x20/gs;
347
348     foreach ( split /\x0D?\x0A/, $message ) {
349
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         }
356     }
357
358     my $status = $response->header('Status');
359
360     if ( $status && $status =~ /^(\d{3})[\x09\x20]+([\x20-\xFF]+)$/ ) {
361         $response->code($1);
362         $response->message($2);
363     }
364
365     if ( !$response->date ) {
366         $response->date(time);
367     }
368
369     if ( $params{headers_only} ) {
370
371         if ( $params{sync} ) {
372
373             my $position = tell( $self->stdout )
374               or croak("Couldn't get file position from stdout handle: '$!'");
375
376             sysseek( $self->stdout, $position, SEEK_SET )
377               or croak("Couldn't seek stdout handle: '$!'");
378         }
379
380         return $response;
381     }
382
383     my $content        = undef;
384     my $content_length = 0;
385
386     while () {
387
388         my $r = $self->stdout->read( $content, 65536, $content_length );
389
390         if ( defined $r ) {
391
392             $content_length += $r;
393
394             last unless $r;
395         }
396         else {
397             croak("Couldn't read response content from stdin handle: '$!'");
398         }
399     }
400
401     if ( $content_length ) {
402
403         $response->content_ref(\$content);
404
405         if ( !$response->content_length ) {
406             $response->content_length($content_length);
407         }
408     }
409
410     return $response;
411 }
412
413 sub restore {
414     my $self = shift;
415
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);
426     }
427
428     return $self;
429 }
430
431 sub restore_environment {
432     my $self = shift;
433
434     no warnings 'uninitialized';
435
436     %ENV = %{ $self->{restore}->{environment} };
437 }
438
439 sub restore_stdin {
440     my $self = shift;
441
442     if ( $self->has_stdin ) {
443
444         my $stdin = $self->{restore}->{stdin};
445
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         }
457
458         if ( $self->should_rewind ) {
459
460             seek( $self->stdin, 0, SEEK_SET )
461               or croak("Couldn't seek stdin handle: '$!'");
462         }
463     }
464 }
465
466 sub 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 {
482
483             my $stdout_ref = $self->{restore}->{stdout_ref};
484
485             *{ $stdout_ref } = $stdout;
486         }
487
488         if ( $self->should_rewind ) {
489
490             seek( $self->stdout, 0, SEEK_SET )
491               or croak("Couldn't seek stdout handle: '$!'");
492         }
493     }
494 }
495
496 sub restore_stderr {
497     my $self = shift;
498
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     }
524 }
525
526 sub DESTROY {
527     my $self = shift;
528
529     if ( $self->should_restore && $self->is_setuped && !$self->is_restored ) {
530         $self->restore;
531     }
532 }
533
534 1;
535
536 __END__
537
538 =head1 NAME
539
540 HTTP::Request::AsCGI - Setup a CGI environment from a HTTP::Request
541
542 =head1 SYNOPSIS
543
544     use CGI;
545     use HTTP::Request;
546     use HTTP::Request::AsCGI;
547
548     my $request = HTTP::Request->new( GET => 'http://www.host.com/' );
549     my $stdout;
550
551     {
552         my $c = HTTP::Request::AsCGI->new($request)->setup;
553         my $q = CGI->new;
554
555         print $q->header,
556               $q->start_html('Hello World'),
557               $q->h1('Hello World'),
558               $q->end_html;
559
560         $stdout = $c->stdout;
561
562         # environment and descriptors will automatically be restored
563         # when $c is destructed.
564     }
565
566     while ( my $line = $stdout->getline ) {
567         print $line;
568     }
569
570 =head1 DESCRIPTION
571
572 Provides a convinient way of setting up an CGI environment from a HTTP::Request.
573
574 =head1 METHODS
575
576 =over 4
577
578 =item new ( $request [, key => value ] )
579
580 Contructor, first argument must be a instance of HTTP::Request
581 followed by optional pairs of environment key and value.
582
583 =item environment
584
585 Returns a hashref containing the environment that will be used in setup.
586 Changing the hashref after setup has been called will have no effect.
587
588 =item setup
589
590 Setups the environment and descriptors.
591
592 =item restore
593
594 Restores the environment and descriptors. Can only be called after setup.
595
596 =item request
597
598 Returns the request given to constructor.
599
600 =item response
601
602 Returns a HTTP::Response. Can only be called after restore.
603
604 =item stdin
605
606 Accessor for handle that will be used for STDIN, must be a real seekable
607 handle with an file descriptor. Defaults to a tempoary IO::File instance.
608
609 =item stdout
610
611 Accessor for handle that will be used for STDOUT, must be a real seekable
612 handle with an file descriptor. Defaults to a tempoary IO::File instance.
613
614 =item stderr
615
616 Accessor for handle that will be used for STDERR, must be a real seekable
617 handle with an file descriptor.
618
619 =back
620
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
633 =head1 THANKS TO
634
635 Thomas L. Shinnick for his valuable win32 testing.
636
637 =head1 AUTHOR
638
639 Christian Hansen, C<ch@ngmedia.com>
640
641 =head1 LICENSE
642
643 This library is free software. You can redistribute it and/or modify
644 it under the same terms as perl itself.
645
646 =cut