Provided by: libpoe-perl_1.3670-1_all bug

NAME

       POE::Filter::HTTPD - parse simple HTTP requests, and serialize HTTP::Response

SYNOPSIS

         #!perl

         use warnings;
         use strict;

         use POE qw(Component::Server::TCP Filter::HTTPD);
         use HTTP::Response;

         POE::Component::Server::TCP->new(
           Port         => 8088,
           ClientFilter => 'POE::Filter::HTTPD',  ### <-- HERE WE ARE!

           ClientInput => sub {
             my $request = $_[ARG0];

             # It's a response for the client if there was a problem.
             if ($request->isa("HTTP::Response")) {
               my $response = $request;

               $request = $response->request;
               warn "ERROR: ", $request->message if $request;

               $_[HEAP]{client}->put($response);
               $_[KERNEL]->yield("shutdown");
               return;
             }

             my $request_fields = '';
             $request->headers()->scan(
               sub {
                 my ($header, $value) = @_;
                 $request_fields .= (
                   "<tr><td>$header</td><td>$value</td></tr>"
                 );
               }
             );

             my $response = HTTP::Response->new(200);
             $response->push_header( 'Content-type', 'text/html' );
             $response->content(
               "<html><head><title>Your Request</title></head>" .
               "<body>Details about your request:" .
               "<table border='1'>$request_fields</table>" .
               "</body></html>"
             );

             $_[HEAP]{client}->put($response);
             $_[KERNEL]->yield("shutdown");
           }
         );

         print "Aim your browser at port 8088 of this host.\n";
         POE::Kernel->run();
         exit;

DESCRIPTION

       POE::Filter::HTTPD interprets input streams as HTTP 0.9, 1.0 or 1.1 requests.  It returns
       a HTTP::Request objects upon successfully parsing a request.

       On failure, it returns an HTTP::Response object describing the failure.  The intention is
       that application code will notice the HTTP::Response and send it back without further
       processing. The erroneous request object is sometimes available via the "$r->request" in
       HTTP::Response method.  This is illustrated in the "SYNOPSIS".

       For output, POE::Filter::HTTPD accepts HTTP::Response objects and returns their
       corresponding streams.

       Please see HTTP::Request and HTTP::Response for details about how to use these objects.

       HTTP headers are not allowed to have UTF-8 characters; they must be ISO-8859-1.
       POE::Filter::HTTPD will convert all UTF-8 into the MIME encoded equivalent.  It uses
       utf8::is_utf8 for detection-8 and Email::MIME::RFC2047::Encoder for convertion.  If utf8
       is not installed, no conversion happens.  If Email::MIME::RFC2047::Encoder is not
       installed, utf8::downgrade is used instead.  In this last case, you will see a warning if
       you try to send UTF-8 headers.

PUBLIC FILTER METHODS

       POE::Filter::HTTPD implements the basic POE::Filter interface.

   new
       new() accepts a list of named parameters.

       "MaxBuffer" sets the maximum amount of data the filter will hold in memory.  Defaults to
       512 MB (536870912 octets).  Because POE::Filter::HTTPD copies all data into memory,
       setting this number to high would allow a malicious HTTPD client to fill all server memory
       and swap.

       "MaxContent" sets the maximum size of the content of an HTTP request.  Defaults to 1 MB
       (1038336 octets).  Because POE::Filter::HTTPD copies all data into memory, setting this
       number to high would allow a malicious HTTPD client to fill all server memory and swap.
       Ignored if "Streaming" is set.

       "Streaming" turns on request streaming mode.  Defaults to off.  In streaming mode this
       filter will return either an HTTP::Request object or a block of content.  The
       HTTP::Request object's content will return empty.  The blocks of content will be parts of
       the request's body, up to Content-Length in size.  You distinguish between request objects
       and content blocks using "Scalar::Util/bless" (See "Streaming request" below).  This
       option supersedes "MaxContent".

CAVEATS

       Some versions of libwww are known to generate invalid HTTP.  For example, this code
       (adapted from the HTTP::Request::Common documentation) will cause an error in a
       POE::Filter::HTTPD daemon:

       NOTE: Using this test with libwww-perl/5.834 showed that it added the proper HTTP/1.1
       data! We're not sure which version of LWP fixed this. This example is valid for older LWP
       installations, beware!

         use HTTP::Request::Common;
         use LWP::UserAgent;

         my $ua = LWP::UserAgent->new();
         $ua->request(POST 'http://example.com', [ foo => 'bar' ]);

       By default, HTTP::Request is HTTP version agnostic. It makes no attempt to add an HTTP
       version header unless you specifically declare a protocol using
       "$request->protocol('HTTP/1.0')".

       According to the HTTP 1.0 RFC (1945), when faced with no HTTP version header, the parser
       is to default to HTTP/0.9.  POE::Filter::HTTPD follows this convention.  In the
       transaction detailed above, the Filter::HTTPD based daemon will return a 400 error since
       POST is not a valid HTTP/0.9 request type.

       Upon handling a request error, it is most expedient and reliable to respond with the error
       and shut down the connection.  Invalid HTTP requests may corrupt the request stream.  For
       example, the absence of a Content-Length header signals that a request has no content.
       Requests with content but without that header will be broken into a content-less request
       and invalid data.  The invalid data may also appear to be a request!  Hilarity will ensue,
       possibly repeatedly, until the filter can find the next valid request.  By shutting down
       the connection on the first sign of error, the client can retry its request with a clean
       connection and filter.

Streaming Request

       Normally POE::Filter::HTTPD reads the entire request content into memory before returning
       the HTTP::Request to your code.  In streaming mode, it will return the content separately,
       as unblessed scalars.  The content may be split up into blocks of varying sizes, depending
       on OS and transport constraints.  Your code can distinguish the request object from the
       content blocks using "blessed" in Scalar::Util.

           use Scalar::Util;
           use POE::Wheel::ReadWrite;
           use POE::Filter:HTTPD;

           $heap->{wheel} = POE::Wheel::ReadWrite->new(
                               InputEvent => 'http_input',
                               Filter => POE::Filter::HTTPD->new( Streaming => 1 ),
                               # ....
                       );

           sub http_input_handler
           {
               my( $heap, $req_or_data ) = @_[ HEAP, ARG0 ];
               if( blessed $req_or_data ) {
                   my $request = $req_or_data;
                   if( $request->isa( 'HTTP::Response') ) {
                       # HTTP error
                       $heap->{wheel}->put( $request );
                   }
                   else {
                       # HTTP request
                       # ....
                   }
               }
               else {
                   my $data = $req_or_data;
                   # ....
               }
           }

       You may trivally create a DoS bug if you hold all content in memory but do not impose a
       maximum Content-Length.  An attacker could send "Content-Length: 1099511627776" (aka 1 TB)
       and keep sending data until all your system's memory and swap is filled.

       Content-Length has been sanitized by POE::Filter::HTTPD so checking it is trivial :

           if( $request->headers( 'Content-Length' ) > 1024*1024 ) {
               my $resp = HTTP::Response->new( RC_REQUEST_ENTITY_TOO_LARGE ),
                                                    "So much content!" )
               $heap->{wheel}->put( $resp );
               return;
           }

       If you want to handle large amounts of data, you should save the content to a file before
       processing it.  You still need to check Content-Length or an attacker might fill up the
       partition.

           use File::Temp qw(tempfile);

           if( blessed $_[ARG0] ) {
               $heap->{request} = $_[ARG0];
               if( $heap->{request}->method eq 'GET' ) {
                   handle_get( $heap );
                   delete $heap->{request};
                   return;
               }
               my( $fh, $file ) = tempfile( "httpd-XXXXXXXX", TMPDIR=>1 );
               $heap->{content_file} = $file;
               $heap->{content_fh} = $fh;
               $heap->{content_size} = 0;
           }
           else {
               return unless $heap->{request};

               $heap->{content_size} += length( $_[ARG0] );
               $heap->{content_fh}->print( $_[ARG0] );
               if( $heap->{content_size} >= $heap->{request}->headers( 'content-length' ) ) {
                   delete $heap->{content_fh};
                   delete $heap->{content_size};

                   # Now we can parse $heap->{content_file}
                   if( $heap->{request}->method eq 'POST' ) {
                       handle_post( $heap );
                   }
                   else {
                       # error ...
                   }
               }
           }

           sub handle_post
           {
               my( $heap ) = @_;
               # Now we have to load and parse $heap->{content_file}

               # Next 6 lines make the data available to CGI->init
               local $ENV{REQUEST_METHOD} = 'POST';
               local $CGI::PERLEX = $CGI::PERLEX = "CGI-PerlEx/Fake";
               local $ENV{CONTENT_TYPE} = $heap->{req}->header( 'content-type' );
               local $ENV{CONTENT_LENGTH} = $heap->{req}->header( 'content-length' );
               my $keep = IO::File->new( "<&STDIN" ) or die "Unable to reopen STDIN: $!";
               open STDIN, "<$heap->{content_file}" or die "Reopening STDIN failed: $!";

               my $qcgi = CGI->new();

               # cleanup
               open STDIN, "<&".$keep->fileno or die "Unable to reopen $keep: $!";
               undef $keep;
               unlink delete $heap->{content_file};

               # now use $q as you would normaly
               my $file = $q->upload( 'field_name' );

               # ....
           }

           sub handle_get
           {
               my( $heap ) = @_;

               # 4 lines to get data into CGI->init
               local $ENV{REQUEST_METHOD} = 'GET';
               local $CGI::PERLEX = $CGI::PERLEX = "CGI-PerlEx/Fake";
               local $ENV{CONTENT_TYPE} = $heap->{req}->header( 'content-type' );
               local $ENV{'QUERY_STRING'} = $heap->{req}->uri->query;

               my $q = CGI->new();

               # now use $q as you would normaly
               # ....
           }

Streaming Response

       It is possible to use POE::Filter::HTTPD for streaming content, but an application can use
       it to send headers and then switch to POE::Filter::Stream.

       From the input handler (the InputEvent handler if you're using wheels, or the ClientInput
       handler for POE::Component::Server::TCP):

         my $response = HTTP::Response->new(200);
         $response->push_header('Content-type', 'audio/x-mpeg');
         $_[HEAP]{client}->put($response);
         $_[HEAP]{client}->set_output_filter(POE::Filter::Stream->new());

       Then the output-flushed handler (FlushEvent for POE::Wheel::ReadWrite, or ClientFlushed
       for POE::Component::Server::TCP) can put() chunks of the stream as needed.

         my $bytes_read = sysread(
           $_[HEAP]{file_to_stream}, my $buffer = '', 4096
         );

         if ($bytes_read) {
           $_[HEAP]{client}->put($buffer);
         }
         else {
           delete $_[HEAP]{file_to_stream};
           $_[KERNEL]->yield("shutdown");
         }

SEE ALSO

       Please see POE::Filter for documentation regarding the base interface.

       The SEE ALSO section in POE contains a table of contents covering the entire POE
       distribution.

       HTTP::Request and HTTP::Response explain all the wonderful things you can do with these
       classes.

BUGS

       Many aspects of HTTP 1.0 and higher are not supported, such as keep-alive.  A simple I/O
       filter can't support keep-alive, for example.  A number of more feature-rich POE HTTP
       servers are on the CPAN.  See
       <http://search.cpan.org/search?query=POE+http+server&mode=dist>

AUTHORS & COPYRIGHTS

       POE::Filter::HTTPD was contributed by Artur Bergman.  Documentation is provided by Rocco
       Caputo.

       Please see POE for more information about authors and contributors.