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