File Coverage

blib/lib/HTTP/Proxy.pm
Criterion Covered Total %
statement 381 419 90.9
branch 124 164 75.6
condition 37 59 62.7
subroutine 53 54 98.1
pod 13 13 100.0
total 608 709 85.7


line stmt bran cond sub pod time code
1             package HTTP::Proxy;
2             $HTTP::Proxy::VERSION = '0.304';
3 69     69   3887703 use HTTP::Daemon;
  69         2879458  
  69         935  
4 69     69   42104 use HTTP::Date qw(time2str);
  69         128  
  69         3837  
5 69     69   20464 use LWP::UserAgent;
  69         212277  
  69         1734  
6 69     69   39359 use LWP::ConnCache;
  69         82465  
  69         2383  
7 69     69   534 use Fcntl ':flock'; # import LOCK_* constants
  69         111  
  69         11084  
8 69     69   39562 use IO::Select;
  69         97080  
  69         4246  
9 69     69   38608 use Sys::Hostname; # hostname()
  69         74964  
  69         4589  
10 69     69   487 use Socket qw( SOL_SOCKET SO_SNDBUF SO_RCVBUF );
  69         123  
  69         5773  
11 69     69   383 use Carp;
  69         96  
  69         3501  
12              
13 69     69   955 use strict;
  69         90  
  69         2395  
14 69         9468 use vars qw( $VERSION @METHODS
15 69     69   295 @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS );
  69         71  
16              
17             require Exporter;
18             @ISA = qw(Exporter);
19             @EXPORT = (); # no export by default
20             @EXPORT_OK = qw( ERROR NONE PROXY STATUS PROCESS SOCKET HEADERS FILTERS
21             DATA CONNECT ENGINE ALL );
22             %EXPORT_TAGS = ( log => [@EXPORT_OK] ); # only one tag
23              
24             my $CRLF = "\015\012"; # "\r\n" is not portable
25              
26             # standard filters
27 69     69   35572 use HTTP::Proxy::HeaderFilter::standard;
  69         151  
  69         2331  
28              
29             # constants used for logging
30 69     69   370 use constant ERROR => -1; # always log
  69         107  
  69         5052  
31 69     69   324 use constant NONE => 0; # never log
  69         78  
  69         2872  
32 69     69   305 use constant PROXY => 1; # proxy information
  69         78  
  69         2774  
33 69     69   288 use constant STATUS => 2; # HTTP status
  69         97  
  69         2605  
34 69     69   291 use constant PROCESS => 4; # sub-process life (and death)
  69         108  
  69         3150  
35 69     69   286 use constant SOCKET => 8; # low-level connections
  69         93  
  69         2730  
36 69     69   299 use constant HEADERS => 16; # HTTP headers
  69         105  
  69         2802  
37 69     69   283 use constant FILTERS => 32; # Messages from filters
  69         110  
  69         3016  
38 69     69   295 use constant DATA => 64; # Data received by the filters
  69         83  
  69         3205  
39 69     69   285 use constant CONNECT => 128; # Data transmitted by the CONNECT method
  69         967  
  69         2872  
40 69     69   295 use constant ENGINE => 256; # Internal information from the Engine
  69         86  
  69         2530  
41 69     69   268 use constant ALL => 511; # All of the above
  69         88  
  69         2502  
42              
43             # modules that need those constants to be defined
44 69     69   28878 use HTTP::Proxy::Engine;
  69         132  
  69         1900  
45 69     69   26939 use HTTP::Proxy::FilterStack;
  69         135  
  69         47766  
46              
47             # Methods we can forward
48             my %METHODS;
49              
50             # HTTP (RFC 2616)
51             $METHODS{http} = [qw( CONNECT DELETE GET HEAD OPTIONS POST PUT TRACE )];
52              
53             # WebDAV (RFC 2518)
54             $METHODS{webdav} = [
55             @{ $METHODS{http} },
56             qw( COPY LOCK MKCOL MOVE PROPFIND PROPPATCH UNLOCK )
57             ];
58              
59             # Delta-V (RFC 3253)
60             $METHODS{deltav} = [
61             @{ $METHODS{webdav} },
62             qw( BASELINE-CONTROL CHECKIN CHECKOUT LABEL MERGE MKACTIVITY
63             MKWORKSPACE REPORT UNCHECKOUT UPDATE VERSION-CONTROL ),
64             ];
65              
66             # the whole method list
67             @METHODS = HTTP::Proxy->known_methods();
68              
69             # useful regexes (from RFC 2616 BNF grammar)
70             my %RX;
71             $RX{token} = qr/[-!#\$%&'*+.0-9A-Z^_`a-z|~]+/;
72             $RX{mime} = qr($RX{token}/$RX{token});
73             $RX{method} = '(?:' . join ( '|', @METHODS ) . ')';
74             $RX{method} = qr/$RX{method}/;
75              
76             sub new {
77 74     74 1 5044240 my $class = shift;
78 74         434 my %params = @_;
79              
80             # some defaults
81 74         1470 my %defaults = (
82             agent => undef,
83             chunk => 4096,
84             daemon => undef,
85             host => 'localhost',
86             logfh => *STDERR,
87             logmask => NONE,
88             max_connections => 0,
89             max_keep_alive_requests => 10,
90             port => 8080,
91             stash => {},
92             timeout => 60,
93             via => undef,
94             x_forwarded_for => 1,
95             );
96              
97             # non modifiable defaults
98 74         710 my $self = bless { conn => 0, loop => 1 }, $class;
99              
100             # support for deprecated stuff
101             {
102 74         136 my %convert = (
  74         2281  
103             maxchild => 'max_clients',
104             maxconn => 'max_connections',
105             maxserve => 'max_keep_alive_requests',
106             );
107 74         669 while( my ($old, $new) = each %convert ) {
108 222 100       903 if( exists $params{$old} ) {
109 5         19 $params{$new} = delete $params{$old};
110 5         1139 carp "$old is deprecated, please use $new";
111             }
112             }
113             }
114              
115             # get attributes
116             $self->{$_} = exists $params{$_} ? delete( $params{$_} ) : $defaults{$_}
117 74 100       1985 for keys %defaults;
118              
119 74 100       408 if (!defined $self->{via}) {
120 73 50       505 $self->{via} =
121             hostname()
122             . ( $self->{port} != 80 ? ":$self->{port}" : '' )
123             . " (HTTP::Proxy/$VERSION)";
124             }
125              
126             # choose an engine with the remaining parameters
127 74         1866 $self->{engine} = HTTP::Proxy::Engine->new( %params, proxy => $self );
128 74         625 $self->log( PROXY, "PROXY", "Selected engine " . ref $self->{engine} );
129              
130 74         473 return $self;
131             }
132              
133             sub known_methods {
134 74     74 1 759 my ( $class, @args ) = @_;
135              
136 74 100       534 @args = map { lc } @args ? @args : ( keys %METHODS );
  214         532  
137             exists $METHODS{$_} || carp "Method group $_ doesn't exist"
138 74   50     700 for @args;
139 74         123 my %seen;
140 74 50       161 return grep { !$seen{$_}++ } map { @{ $METHODS{$_} || [] } } @args;
  3487         5224  
  214         188  
  214         1087  
141             }
142              
143             sub timeout {
144 65     65 1 1137 my $self = shift;
145 65         752 my $old = $self->{timeout};
146 65 100       204 if (@_) {
147 1         3 $self->{timeout} = shift;
148 1 50       4 $self->agent->timeout( $self->{timeout} ) if $self->agent;
149             }
150 65         676 return $old;
151             }
152              
153             sub url {
154 38     38 1 1092379 my $self = shift;
155 38 100       402 if ( not defined $self->daemon ) {
156 1         205 carp "HTTP daemon not started yet";
157 1         47 return undef;
158             }
159 37         162 return $self->daemon->url;
160             }
161              
162             # normal accessors
163             for my $attr ( qw(
164             agent chunk daemon host logfh port request response hop_headers
165             logmask via x_forwarded_for client_headers engine
166             max_connections max_keep_alive_requests
167             )
168             )
169             {
170 69     69   489 no strict 'refs';
  69         151  
  69         6985  
171             *{"HTTP::Proxy::$attr"} = sub {
172 4262     4262   307769 my $self = shift;
173 4262         9227 my $old = $self->{$attr};
174 4262 100       9319 $self->{$attr} = shift if @_;
175 4262         27777 return $old;
176             }
177             }
178              
179             # read-only accessors
180             for my $attr (qw( conn loop client_socket )) {
181 69     69   328 no strict 'refs';
  69         113  
  69         7186  
182 466     466   4477 *{"HTTP::Proxy::$attr"} = sub { $_[0]{$attr} }
183             }
184              
185 4     4 1 3194 sub max_clients { shift->engine->max_clients( @_ ) }
186              
187             # deprecated methods are still supported
188             {
189             my %convert = (
190             maxchild => 'max_clients',
191             maxconn => 'max_connections',
192             maxserve => 'max_keep_alive_requests',
193             );
194             while ( my ( $old, $new ) = each %convert ) {
195 69     69   312 no strict 'refs';
  69         95  
  69         247197  
196             *$old = sub {
197 3     3   399 carp "$old is deprecated, please use $new";
198 3         168 goto \&$new;
199             };
200             }
201             }
202              
203             sub stash {
204 8     8 1 37 my $stash = shift->{stash};
205 8 100       43 return $stash unless @_;
206 4 100       27 return $stash->{ $_[0] } if @_ == 1;
207 1         7 return $stash->{ $_[0] } = $_[1];
208             }
209              
210 6     6 1 26 sub new_connection { ++$_[0]{conn} }
211              
212             sub start {
213 36     36 1 130521 my $self = shift;
214              
215 36         1564 $self->init;
216 36     0   3459 $SIG{INT} = $SIG{TERM} = sub { $self->{loop} = 0 };
  0         0  
217              
218             # the main loop
219 36         1149 my $engine = $self->engine;
220 36 50       4542 $engine->start if $engine->can('start');
221 36         433 while( $self->loop ) {
222 104         929 $engine->run;
223 81 100 66     856 last if $self->max_connections && $self->conn >= $self->max_connections;
224             }
225 13 50       291 $engine->stop if $engine->can('stop');
226              
227 13         51 $self->log( STATUS, "STATUS",
228             "Processed " . $self->conn . " connection(s)" );
229              
230 13         361 return $self->conn;
231             }
232              
233             # semi-private init method
234             sub init {
235 122     122 1 620 my $self = shift;
236              
237             # must be run only once
238 122 100       1573 return if $self->{_init}++;
239              
240 62 50       231 $self->_init_daemon if ( !defined $self->daemon );
241 62 50       235 $self->_init_agent if ( !defined $self->agent );
242              
243             # specific agent config
244 62         197 $self->agent->requests_redirectable( [] );
245 62         1043 $self->agent->agent(''); # for TRACE support
246 62         3654 $self->agent->protocols_allowed( [qw( http https ftp gopher )] );
247              
248             # standard header filters
249 62         993 $self->{headers}{request} = HTTP::Proxy::FilterStack->new;
250 62         232 $self->{headers}{response} = HTTP::Proxy::FilterStack->new;
251              
252             # the same standard filter is used to handle headers
253 62         853 my $std = HTTP::Proxy::HeaderFilter::standard->new();
254 62         336 $std->proxy( $self );
255 62     75   633 $self->{headers}{request}->push( [ sub { 1 }, $std ] );
  75         310  
256 62     75   386 $self->{headers}{response}->push( [ sub { 1 }, $std ] );
  75         369  
257              
258             # standard body filters
259 62         231 $self->{body}{request} = HTTP::Proxy::FilterStack->new(1);
260 62         202 $self->{body}{response} = HTTP::Proxy::FilterStack->new(1);
261              
262 62         183 return;
263             }
264              
265             #
266             # private init methods
267             #
268              
269             sub _init_daemon {
270 64     64   106 my $self = shift;
271 64         262 my %args = (
272             LocalAddr => $self->host,
273             LocalPort => $self->port,
274             ReuseAddr => 1,
275             );
276 64 50       186 delete $args{LocalPort} unless $self->port; # 0 means autoselect
277 64 50       615 my $daemon = HTTP::Daemon->new(%args)
278             or die "Cannot initialize proxy daemon: $!";
279 64         34061 $self->daemon($daemon);
280              
281 64         135 return $daemon;
282             }
283              
284             sub _init_agent {
285 64     64   136 my $self = shift;
286 64 50       326 my $agent = LWP::UserAgent->new(
287             env_proxy => 1,
288             keep_alive => 2,
289             parse_head => 0,
290             timeout => $self->timeout,
291             )
292             or die "Cannot initialize proxy agent: $!";
293 64         585666 $self->agent($agent);
294 64         114 return $agent;
295             }
296              
297             # This is the internal "loop" that lets the child process process the
298             # incoming connections.
299              
300             sub serve_connections {
301 29     29 1 2516 my ( $self, $conn ) = @_;
302 29         196 my $response;
303 29         511 $self->{client_socket} = $conn; # read-only
304 29         2525 $self->log( SOCKET, "SOCKET", "New connection from " . $conn->peerhost
305             . ":" . $conn->peerport );
306              
307 29         285 my ( $last, $served ) = ( 0, 0 );
308              
309 29         718 while ( $self->loop() ) {
310 90         286 my $req;
311             {
312 90         222 local $SIG{INT} = local $SIG{TERM} = 'DEFAULT';
  90         2425  
313 90         1281 $req = $conn->get_request();
314             }
315              
316 90         2272928 $served++;
317              
318             # initialisation
319 90         631 $self->request($req);
320 90         547 $self->response(undef);
321              
322             # Got a request?
323 90 100       529 unless ( defined $req ) {
324 13 50       45 $self->log( SOCKET, "SOCKET",
325             "Getting request failed: " . $conn->reason )
326             if $conn->reason ne 'No more requests from this connection';
327 13         334 return;
328             }
329 77 100       737 $self->log( STATUS, "REQUEST", $req->method . ' '
330             . ( $req->method eq 'CONNECT' ? $req->uri->host_port : $req->uri ) );
331              
332             # can we forward this method?
333 77 50       529 if ( !grep { $_ eq $req->method } @METHODS ) {
  2002         14814  
334 0         0 $response = HTTP::Response->new( 501, 'Not Implemented' );
335 0         0 $response->content_type( "text/plain" );
336 0         0 $response->content(
337             "Method " . $req->method . " is not supported by this proxy." );
338 0         0 $self->response($response);
339 0         0 goto SEND;
340             }
341              
342             # transparent proxying support
343 77 100       1292 if( not defined $req->uri->scheme ) {
344 5 100       277 if( my $host = $req->header('Host') ) {
345 4         247 $req->uri->scheme( 'http' );
346 4         615 $req->uri->host( $host );
347             }
348             else {
349 1         80 $response = HTTP::Response->new( 400, 'Bad request' );
350 1         126 $response->content_type( "text/plain" );
351 1         69 $response->content("Can't do transparent proxying without a Host: header.");
352 1         42 $self->response($response);
353 1         18 goto SEND;
354             }
355             }
356              
357             # can we serve this protocol?
358 76 100       4377 if ( !$self->is_protocol_supported( my $s = $req->uri->scheme ) )
359             {
360             # should this be 400 Bad Request?
361 1         20 $response = HTTP::Response->new( 501, 'Not Implemented' );
362 1         201 $response->content_type( "text/plain" );
363 1         69 $response->content("Scheme $s is not supported by this proxy.");
364 1         37 $self->response($response);
365 1         11 goto SEND;
366             }
367              
368             # select the request filters
369 75         1274 $self->{$_}{request}->select_filters( $req ) for qw( headers body );
370              
371             # massage the request
372 75         507 $self->{headers}{request}->filter( $req->headers, $req );
373              
374             # FIXME I don't know how to get the LWP::Protocol object...
375             # NOTE: the request is always received in one piece
376 75         710 $self->{body}{request}->filter( $req->content_ref, $req, undef );
377 75         414 $self->{body}{request}->eod; # end of data
378 75         283 $self->log( HEADERS, "REQUEST", $req->headers->as_string );
379              
380             # CONNECT method is a very special case
381 75 100 66     266 if( ! defined $self->response and $req->method eq 'CONNECT' ) {
382 1         22 $last = $self->_handle_CONNECT($served);
383 1 50       6 return if $last;
384             }
385              
386             # the header filters created a response,
387             # we won't contact the origin server
388             # FIXME should the response header and body be filtered?
389 74 50       1075 goto SEND if defined $self->response;
390              
391             # FIXME - don't forward requests to ourselves!
392              
393             # pop a response
394 74         198 my ( $sent, $chunked ) = ( 0, 0 );
395             $response = $self->agent->simple_request(
396             $req,
397             sub {
398 61     61   2369008 my ( $data, $response, $proto ) = @_;
399              
400             # first time, filter the headers
401 61 100       258 if ( !$sent ) {
402 29         63 $sent++;
403 29         190 $self->response( $response );
404              
405             # select the response filters
406             $self->{$_}{response}->select_filters( $response )
407 29         294 for qw( headers body );
408              
409 29         173 $self->{headers}{response}
410             ->filter( $response->headers, $response );
411 29         318 ( $last, $chunked ) =
412             $self->_send_response_headers( $served );
413             }
414              
415             # filter and send the data
416 61         446 $self->log( DATA, "DATA",
417             "got " . length($data) . " bytes of body data" );
418 61         399 $self->{body}{response}->filter( \$data, $response, $proto );
419 61 100       234 if ($chunked) {
420 48 50       3444 printf $conn "%x$CRLF%s$CRLF", length($data), $data
421             if length($data); # the filter may leave nothing
422             }
423 13         1442 else { print $conn $data; }
424             },
425 74         246 $self->chunk
426             );
427              
428             # remove the header added by LWP::UA before it sends the response back
429 74         6005383 $response->remove_header('Client-Date');
430              
431             # the callback is not called by LWP::UA->request
432             # in some cases (HEAD, redirect, error responses have no body)
433 74 100       2710 if ( !$sent ) {
434 45         330 $self->response($response);
435             $self->{$_}{response}->select_filters( $response )
436 45         463 for qw( headers body );
437 45         237 $self->{headers}{response}
438             ->filter( $response->headers, $response );
439             }
440              
441             # do a last pass, in case there was something left in the buffers
442 74         260 my $data = ""; # FIXME $protocol is undef here too
443 74         513 $self->{body}{response}->filter_last( \$data, $response, undef );
444 74 50       347 if ( length $data ) {
445 0 0       0 if ($chunked) {
446 0         0 printf $conn "%x$CRLF%s$CRLF", length($data), $data;
447             }
448 0         0 else { print $conn $data; }
449             }
450              
451             # last chunk
452 74 100       15314 print $conn "0$CRLF$CRLF" if $chunked; # no trailers either
453 74         322 $self->response($response);
454              
455             # what about X-Died and X-Content-Range?
456 74 50       369 if( my $died = $response->header('X-Died') ) {
457 0         0 $self->log( ERROR, "ERROR", $died );
458 0         0 $sent = 0;
459 0         0 $response = HTTP::Response->new( 500, "Proxy filter error" );
460 0         0 $response->content_type( "text/plain" );
461 0         0 $response->content($died);
462 0         0 $self->response($response);
463             }
464              
465             SEND:
466              
467 76         4802 $response = $self->response ;
468              
469             # responses that weren't filtered through callbacks
470             # (empty body or error)
471             # FIXME some error response headers might not be filtered
472 76 100       319 if ( !$sent ) {
473 47         309 ($last, $chunked) = $self->_send_response_headers( $served );
474 47         689 my $content = $response->content;
475 47 100       969 if ($chunked) {
476 37 100       1839 printf $conn "%x$CRLF%s$CRLF", length($content), $content
477             if length($content); # the filter may leave nothing
478 37         2941 print $conn "0$CRLF$CRLF";
479             }
480 10         294 else { print $conn $content; }
481             }
482              
483             # FIXME ftp, gopher
484 76 50 66     477 $conn->print( $response->content )
      33        
485             if defined $req->uri->scheme
486             and $req->uri->scheme =~ /^(?:ftp|gopher)$/
487             and $response->is_success;
488              
489 76 100 100     6596 $self->log( SOCKET, "SOCKET", "Connection closed by the proxy" ), last
490             if $last || $served >= $self->max_keep_alive_requests;
491             }
492 15 50 66     139 $self->log( SOCKET, "SOCKET", "Connection closed by the client" )
493             if !$last
494             and $served < $self->max_keep_alive_requests;
495 15         810 $self->log( PROCESS, "PROCESS", "Served $served requests" );
496 15         215 $conn->close;
497             }
498              
499             # INTERNAL METHOD
500             # send the response headers for the proxy
501             # expects $served (number of requests served)
502             # returns $last and $chunked (last request served, chunked encoding)
503             sub _send_response_headers {
504 77     77   151 my ( $self, $served ) = @_;
505 77         199 my ( $last, $chunked ) = ( 0, 0 );
506 77         329 my $conn = $self->client_socket;
507 77         255 my $response = $self->response;
508              
509             # correct headers
510 77 100       649 $response->remove_header("Content-Length")
511             if $self->{body}{response}->will_modify();
512 77 100       475 $response->header( Server => "HTTP::Proxy/$VERSION" )
513             unless $response->header( 'Server' );
514 77 100       3782 $response->header( Date => time2str(time) )
515             unless $response->header( 'Date' );
516              
517             # this is adapted from HTTP::Daemon
518 77 50       4537 if ( $conn->antique_client ) { $last++ }
  0         0  
519             else {
520 77         1090 my $code = $response->code;
521 77         999 $conn->send_status_line( $code, $response->message,
522             $self->request()->protocol() );
523 77 100 100     21933 if ( $code =~ /^(1\d\d|[23]04)$/ ) {
    100          
524              
525             # make sure content is empty
526 2         14 $response->remove_header("Content-Length");
527 2         73 $response->content('');
528             }
529             elsif ( $response->request && $response->request->method eq "HEAD" )
530             { # probably OK, says HTTP::Daemon
531             }
532             else {
533 71 100       2825 if ( $conn->proto_ge("HTTP/1.1") ) {
534 65         2005 $chunked++;
535 65         292 $response->push_header( "Transfer-Encoding" => "chunked" );
536 65 100       2330 $response->push_header( "Connection" => "close" )
537             if $served >= $self->max_keep_alive_requests;
538             }
539             else {
540 6         205 $last++;
541 6         24 $conn->force_last_request;
542             }
543             }
544 77         1220 print $conn $response->headers_as_string($CRLF);
545 77         26884 print $conn $CRLF; # separates headers and content
546             }
547 77         779 $self->log( STATUS, "RESPONSE", $response->status_line );
548 77         726 $self->log( HEADERS, "RESPONSE", $response->headers->as_string );
549 77         402 return ($last, $chunked);
550             }
551              
552             # INTERNAL method
553             # FIXME no man-in-the-middle for now
554             sub _handle_CONNECT {
555 1     1   2 my ($self, $served) = @_;
556 1         1 my $last = 0;
557              
558 1         2 my $conn = $self->client_socket;
559 1         3 my $req = $self->request;
560 1         2 my $upstream;
561              
562             # connect upstream
563 1 50       4 if ( my $up = $self->agent->proxy('http') ) {
564              
565             # clean up authentication info from proxy URL
566 0         0 $up =~ s{^http://[^/\@]*\@}{http://};
567              
568             # forward to upstream proxy
569 0         0 $self->log( PROXY, "PROXY",
570             "Forwarding CONNECT request to next proxy: $up" );
571 0         0 my $response = $self->agent->simple_request($req);
572              
573             # check the upstream proxy's response
574 0         0 my $code = $response->code;
575 0 0       0 if ( $code == 407 ) { # don't forward Proxy Authentication requests
    0          
576 0         0 my $response_407 = $response->as_string;
577 0         0 $response_407 =~ s/^Client-.*$//mg;
578 0         0 $response = HTTP::Response->new(502);
579 0         0 $response->content_type("text/plain");
580 0         0 $response->content( "Upstream proxy ($up) "
581             . "requested authentication:\n\n"
582             . $response_407 );
583 0         0 $self->response($response);
584 0         0 return $last;
585             }
586             elsif ( $code != 200 ) { # forward every other failure
587 0         0 $self->response($response);
588 0         0 return $last;
589             }
590              
591 0         0 $upstream = $response->{client_socket};
592             }
593             else { # direct connection
594 1         50 $upstream = IO::Socket::INET->new( PeerAddr => $req->uri->host_port );
595             }
596              
597             # no upstream socket obtained
598 1 50       683 if( !$upstream ) {
599 0         0 my $response = HTTP::Response->new( 500 );
600 0         0 $response->content_type( "text/plain" );
601 0         0 $response->content( "CONNECT failed: $@");
602 0         0 $self->response($response);
603 0         0 return $last;
604             }
605              
606 1         23 $upstream->setsockopt( SOL_SOCKET, SO_SNDBUF,
607             $conn->getsockopt( SOL_SOCKET, SO_RCVBUF ) );
608              
609             # send the response headers (FIXME more headers required?)
610 1         44 my $response = HTTP::Response->new(200);
611 1         87 $self->response($response);
612 1         6 $self->{$_}{response}->select_filters( $response ) for qw( headers body );
613              
614 1         8 $self->_send_response_headers( $served );
615              
616             # we now have a TCP connection
617 1         1 $last = 1;
618              
619 1         22 my $select = IO::Select->new;
620 1         8 for ( $conn, $upstream ) {
621 2         53 $_->autoflush(1);
622 2         73 $_->blocking(0);
623 2         38 $select->add($_);
624             }
625              
626             # loop while there is data
627 1         23 while ( my @ready = $select->can_read ) {
628 2         93 for (@ready) {
629 2         6 my $data = "";
630 2 50       11 my ($sock, $peer, $from ) = $conn eq $_
631             ? ( $conn, $upstream, "client" )
632             : ( $upstream, $conn, "server" );
633              
634             # read the data
635 2         14 my $read = $sock->sysread( $data, 4096 );
636              
637             # check for errors
638 2 50       22 if(not defined $read ) {
639 0         0 $self->log( ERROR, "CONNECT", "Read undef from $from ($!)" );
640 0         0 next;
641             }
642              
643             # end of connection
644 2 100       4 if ( $read == 0 ) {
645 1         9 $_->close for ( $sock, $peer );
646 1         81 $select->remove( $sock, $peer );
647 1         68 $self->log( SOCKET, "CONNECT", "Connection closed by the $from" );
648 1         3 $self->log( PROCESS, "PROCESS", "Served $served requests" );
649 1         4 next;
650             }
651              
652             # proxy the data
653 1         4 $self->log( CONNECT, "CONNECT", "$read bytes received from $from" );
654 1         12 $peer->syswrite($data, length $data);
655             }
656             }
657 1         7 $self->log( CONNECT, "CONNECT", "End of CONNECT proxyfication");
658 1         24 return $last;
659             }
660              
661             sub push_filter {
662 31     31 1 3489 my $self = shift;
663 31         506 my %arg = (
664             mime => 'text/*',
665             method => join( ',', @METHODS ),
666             scheme => 'http',
667             host => '',
668             path => '',
669             query => '',
670             );
671              
672             # parse parameters
673 31         136 for( my $i = 0; $i < @_ ; $i += 2 ) {
674 54 100       395 next if $_[$i] !~ /^(mime|method|scheme|host|path|query)$/;
675 19         58 $arg{$_[$i]} = $_[$i+1];
676 19         36 splice @_, $i, 2;
677 19         53 $i -= 2;
678             }
679 31 100       385 croak "Odd number of arguments" if @_ % 2;
680              
681             # the proxy must be initialised
682 30         107 $self->init;
683              
684             # prepare the variables for the closure
685 30         128 my ( $mime, $method, $scheme, $host, $path, $query ) =
686             @arg{qw( mime method scheme host path query )};
687              
688 30 50 33     235 if ( defined $mime && $mime ne '' ) {
689 30 100       267 $mime =~ m!/! or croak "Invalid MIME type definition: $mime";
690 29         257 $mime =~ s/\*/$RX{token}/; #turn it into a regex
691 29         715 $mime = qr/^$mime(?:$|\s*;?)/;
692             }
693              
694 29         837 my @method = split /\s*,\s*/, $method;
695 29 100       94 for (@method) { croak "Invalid method: $_" if !/$RX{method}/ }
  704         2969  
696 28 50       234 $method = @method ? '(?:' . join ( '|', @method ) . ')' : '';
697 28         1700 $method = qr/^$method$/;
698              
699 28         245 my @scheme = split /\s*,\s*/, $scheme;
700 28         65 for (@scheme) {
701 28 100       117 croak "Unsupported scheme: $_"
702             if !$self->is_protocol_supported($_);
703             }
704 27 50       166 $scheme = @scheme ? '(?:' . join ( '|', @scheme ) . ')' : '';
705 27         284 $scheme = qr/$scheme/;
706              
707 27   100     174 $host ||= '.*'; $host = qr/$host/i;
  27         173  
708 27   50     161 $path ||= '.*'; $path = qr/$path/;
  27         115  
709 27   50     153 $query ||= '.*'; $query = qr/$query/;
  27         120  
710              
711             # push the filter and its match method on the correct stack
712 27         105 while(@_) {
713 31         83 my ($message, $filter ) = (shift, shift);
714 31 100       399 croak "'$message' is not a filter stack"
715             unless $message =~ /^(request|response)$/;
716              
717 30 100 66     833 croak "Not a Filter reference for filter queue $message"
      66        
718             unless ref( $filter )
719             && ( $filter->isa('HTTP::Proxy::HeaderFilter')
720             || $filter->isa('HTTP::Proxy::BodyFilter') );
721              
722 29         58 my $stack;
723 29 100       141 $stack = 'headers' if $filter->isa('HTTP::Proxy::HeaderFilter');
724 29 100       133 $stack = 'body' if $filter->isa('HTTP::Proxy::BodyFilter');
725              
726             # MIME can only match on response
727 29         44 my $mime = $mime;
728 29 100       90 undef $mime if $message eq 'request';
729              
730             # compute the match sub as a closure
731             # for $self, $mime, $method, $scheme, $host, $path
732             my $match = sub {
733 18 50 50 18   140 return 0
      33        
734             if ( defined $mime )
735             && ( $self->response->content_type || '' ) !~ $mime;
736 18 50 50     831 return 0 if ( $self->{request}->method || '' ) !~ $method;
737 18 50 50     310 return 0 if ( $self->{request}->uri->scheme || '' ) !~ $scheme;
738 18 50 100     844 return 0 if ( $self->{request}->uri->authority || '' ) !~ $host;
739 18 50 50     594 return 0 if ( $self->{request}->uri->path || '' ) !~ $path;
740 18 50 50     391 return 0 if ( $self->{request}->uri->query || '' ) !~ $query;
741 18         463 return 1; # it's a match
742 29         178 };
743              
744             # push it on the corresponding FilterStack
745 29         245 $self->{$stack}{$message}->push( [ $match, $filter ] );
746 29         201 $filter->proxy( $self );
747             }
748             }
749              
750             sub is_protocol_supported {
751 104     104 1 1924 my ( $self, $scheme ) = @_;
752 104         257 my $ok = 1;
753 104 100       680 if ( !$self->agent->is_protocol_supported($scheme) ) {
754              
755             # double check, in case a dummy scheme was added
756             # to be handled directly by a filter
757 2         91 $ok = 0;
758 2   33     4 $scheme eq $_ && $ok++ for @{ $self->agent->protocols_allowed };
  2         7  
759             }
760 104         369342 $ok;
761             }
762              
763             sub log {
764 701     701 1 31143 my $self = shift;
765 701         1309 my $level = shift;
766 701         2527 my $fh = $self->logfh;
767              
768 701 100 100     2434 return unless $self->logmask & $level || $level == ERROR;
769              
770 20         27 my ( $prefix, $msg ) = ( @_, '' );
771 20         40 my @lines = split /\n/, $msg;
772 20 50       36 @lines = ('') if not @lines;
773              
774 20         53 flock( $fh, LOCK_EX );
775 20         452 print $fh "[" . localtime() . "] ($$) $prefix: $_\n" for @lines;
776 20         63 flock( $fh, LOCK_UN );
777             }
778              
779             1;
780              
781             __END__