File Coverage

blib/lib/LWPx/Protocol/http_paranoid.pm
Criterion Covered Total %
statement 122 222 54.9
branch 33 128 25.7
condition 8 44 18.1
subroutine 14 16 87.5
pod 1 3 33.3
total 178 413 43.1


line stmt bran cond sub pod time code
1             # $Id: http_paranoid.pm 2 2005-06-01 23:12:25Z bradfitz $
2             #
3              
4             package LWPx::Protocol::http_paranoid;
5              
6 1     1   7 use strict;
  1         1  
  1         111  
7              
8             require LWP::Debug;
9             require HTTP::Response;
10             require HTTP::Status;
11             require Net::HTTP;
12              
13 1     1   7 use vars qw(@ISA $TOO_LATE $TIME_REMAIN);
  1         2  
  1         3873  
14              
15             require LWP::Protocol;
16             @ISA = qw(LWP::Protocol);
17              
18             my $CRLF = "\015\012";
19              
20             # lame hack using globals in this package to communicate to sysread in the
21             # package at bottom, but whatchya gonna do? Don't want to go modify
22             # Net::HTTP::* to pass explicit timeouts to all the sysreads.
23             sub _set_time_remain {
24 26     26   40 my $now = time;
25 26 50       79 return unless defined $TOO_LATE;
26 26         42 $TIME_REMAIN = $TOO_LATE - $now;
27 26 50       74 $TIME_REMAIN = 0 if $TIME_REMAIN < 0;
28             }
29              
30             sub _new_socket
31             {
32 9     9   22 my($self, $host, $port, $timeout, $request) = @_;
33              
34 9         32 my $conn_cache = $self->{ua}{conn_cache};
35 9 50       25 if ($conn_cache) {
36 0 0       0 if (my $sock = $conn_cache->withdraw("http", "$host:$port")) {
37 0 0 0     0 return $sock if $sock && !$sock->can_read(0);
38             # if the socket is readable, then either the peer has closed the
39             # connection or there are some garbage bytes on it. In either
40             # case we abandon it.
41 0         0 $sock->close;
42             }
43             }
44              
45 9         52 my @addrs = $self->{ua}->_resolve($host, $request, $timeout);
46 7 50       23 unless (@addrs) {
47 0         0 die "Can't connect to $host:$port (No suitable addresses found)";
48             }
49              
50 7         9 my $sock;
51 7         40 local($^W) = 0; # IO::Socket::INET can be noisy
52              
53 7   66     42 while (! $sock && @addrs) {
54 7         13 my $addr = shift @addrs;
55              
56             my $conn_timeout = $request->{_timebegin} ?
57 7 50       24 (time() - $request->{_timebegin}) :
58             $timeout;
59              
60 7         25 $sock = $self->socket_class->new(PeerAddr => $addr,
61             PeerPort => $port,
62             Proto => 'tcp',
63             Timeout => $conn_timeout,
64             KeepAlive => !!$conn_cache,
65             SendTE => 1,
66             );
67             }
68              
69 7 50       59179 unless ($sock) {
70             # IO::Socket::INET leaves additional error messages in $@
71 0         0 $@ =~ s/^.*?: //;
72 0         0 die "Can't connect to $host:$port ($@)";
73             }
74              
75             # perl 5.005's IO::Socket does not have the blocking method.
76 7         16 eval { $sock->blocking(0); };
  7         22  
77              
78 7         80 $sock;
79             }
80              
81             sub socket_class
82             {
83 7     7 0 12 my $self = shift;
84 7   33     116 (ref($self) || $self) . "::Socket";
85             }
86              
87             sub _get_sock_info
88             {
89 7     7   13 my($self, $res, $sock) = @_;
90 7 50       113 if (defined(my $peerhost = $sock->peerhost)) {
91 7         535 $res->header("Client-Peer" => "$peerhost:" . $sock->peerport);
92             }
93             }
94              
95             sub _fixup_header
96             {
97 7     7   17 my($self, $h, $url, $proxy) = @_;
98              
99             # Extract 'Host' header
100 7         33 my $hhost = $url->authority;
101 7 50       446 if ($hhost =~ s/^([^\@]*)\@//) { # get rid of potential "user:pass@"
102             # add authorization header if we need them. HTTP URLs do
103             # not really support specification of user and password, but
104             # we allow it.
105 0 0 0     0 if (defined($1) && not $h->header('Authorization')) {
106 0         0 require URI::Escape;
107 0         0 $h->authorization_basic(map URI::Escape::uri_unescape($_),
108             split(":", $1, 2));
109             }
110             }
111 7         31 $h->init_header('Host' => $hhost);
112              
113             }
114              
115             sub hlist_remove {
116 0     0 0 0 my($hlist, $k) = @_;
117 0         0 $k = lc $k;
118 0         0 for (my $i = @$hlist - 2; $i >= 0; $i -= 2) {
119 0 0       0 next unless lc($hlist->[$i]) eq $k;
120 0         0 splice(@$hlist, $i, 2);
121             }
122             }
123              
124             sub request
125             {
126 9     9 1 16 my($self, $request, $proxy, $arg, $size, $timeout) = @_;
127 9         30 LWP::Debug::trace('()');
128              
129             # paranoid: now $timeout means total time, not just between bytes coming in.
130             # avoids attacker servers from tarpitting a service that fetches URLs.
131 9         31 $TOO_LATE = undef;
132 9         15 $TIME_REMAIN = undef;
133 9 50       23 if ($timeout) {
134 9   33     31 my $start_time = $request->{_time_begin} || time();
135 9         18 $TOO_LATE = $start_time + $timeout;
136             }
137              
138 9   50     41 $size ||= 4096;
139              
140             # check method
141 9         34 my $method = $request->method;
142 9 50       127 unless ($method =~ /^[A-Za-z0-9_!\#\$%&\'*+\-.^\`|~]+$/) { # HTTP token
143 0         0 return new HTTP::Response &HTTP::Status::RC_BAD_REQUEST,
144             'Library does not allow method ' .
145             "$method for 'http:' URLs";
146             }
147              
148 9         28 my $url = $request->url;
149 9         53 my($host, $port, $fullpath);
150              
151 9         31 $host = $url->host;
152 9         257 $port = $url->port;
153 9         284 $fullpath = $url->path_query;
154 9 50       132 $fullpath = "/$fullpath" unless $fullpath =~ m,^/,;
155              
156             # connect to remote sites
157 9         32 my $socket = $self->_new_socket($host, $port, $timeout, $request);
158              
159 7         12 my @h;
160 7         34 my $request_headers = $request->headers->clone;
161 7         521 $self->_fixup_header($request_headers, $url, $proxy);
162              
163             $request_headers->scan(sub {
164 14     14   257 my($k, $v) = @_;
165 14         28 $k =~ s/^://;
166 14         19 $v =~ s/\n/ /g;
167 14         35 push(@h, $k, $v);
168 7         786 });
169              
170 7         147 my $content_ref = $request->content_ref;
171 7 50       103 $content_ref = $$content_ref if ref($$content_ref);
172 7         10 my $chunked;
173             my $has_content;
174              
175 7 50       32 if (ref($content_ref) eq 'CODE') {
176 0         0 my $clen = $request_headers->header('Content-Length');
177 0 0       0 $has_content++ if $clen;
178 0 0       0 unless (defined $clen) {
179 0         0 push(@h, "Transfer-Encoding" => "chunked");
180 0         0 $has_content++;
181 0         0 $chunked++;
182             }
183             }
184             else {
185             # Set (or override) Content-Length header
186 7         31 my $clen = $request_headers->header('Content-Length');
187 7 50 33     312 if (defined($$content_ref) && length($$content_ref)) {
    50          
188 0         0 $has_content++;
189 0 0 0     0 if (!defined($clen) || $clen ne length($$content_ref)) {
190 0 0       0 if (defined $clen) {
191 0         0 warn "Content-Length header value was wrong, fixed";
192 0         0 hlist_remove(\@h, 'Content-Length');
193             }
194 0         0 push(@h, 'Content-Length' => length($$content_ref));
195             }
196             }
197             elsif ($clen) {
198 0         0 warn "Content-Length set when there is not content, fixed";
199 0         0 hlist_remove(\@h, 'Content-Length');
200             }
201             }
202              
203 7         43 my $req_buf = $socket->format_request($method, $fullpath, @h);
204             #print "------\n$req_buf\n------\n";
205              
206             # XXX need to watch out for write timeouts
207             # FIXME_BRAD: make it non-blocking and select during the write
208             {
209 7         45932 my $n = $socket->syswrite($req_buf, length($req_buf));
  7         78  
210 7 50       674 die $! unless defined($n);
211 7 50       29 die "short write" unless $n == length($req_buf);
212             #LWP::Debug::conns($req_buf);
213             }
214              
215 7         11 my($code, $mess, @junk);
216 0         0 my $drop_connection;
217              
218 7 50       19 if ($has_content) {
219 0         0 my $write_wait = 0;
220 0 0 0     0 $write_wait = 2
221             if ($request_headers->header("Expect") || "") =~ /100-continue/;
222              
223 0         0 my $eof;
224             my $wbuf;
225 0         0 my $woffset = 0;
226 0 0       0 if (ref($content_ref) eq 'CODE') {
227 0         0 my $buf = &$content_ref();
228 0 0       0 $buf = "" unless defined($buf);
229 0 0       0 $buf = sprintf "%x%s%s%s", length($buf), $CRLF, $buf, $CRLF
230             if $chunked;
231 0         0 $wbuf = \$buf;
232             }
233             else {
234 0         0 $wbuf = $content_ref;
235 0         0 $eof = 1;
236             }
237              
238 0         0 my $fbits = '';
239 0         0 vec($fbits, fileno($socket), 1) = 1;
240              
241 0         0 while ($woffset < length($$wbuf)) {
242              
243 0         0 my $time_before;
244              
245 0         0 my $now = time();
246 0 0       0 if ($now > $TOO_LATE) {
247 0         0 die "Request took too long.";
248             }
249              
250 0         0 my $sel_timeout = $TOO_LATE - $now;
251 0 0       0 if ($write_wait) {
252 0         0 $time_before = time;
253 0 0       0 $sel_timeout = $write_wait if $write_wait < $sel_timeout;
254             }
255              
256 0         0 my $rbits = $fbits;
257 0 0       0 my $wbits = $write_wait ? undef : $fbits;
258 0         0 my $nfound = select($rbits, $wbits, undef, $sel_timeout);
259 0 0       0 unless (defined $nfound) {
260 0         0 die "select failed: $!";
261             }
262              
263 0 0       0 if ($write_wait) {
264 0         0 $write_wait -= time - $time_before;
265 0 0       0 $write_wait = 0 if $write_wait < 0;
266             }
267              
268 0 0 0     0 if (defined($rbits) && $rbits =~ /[^\0]/) {
269             # readable
270 0         0 my $buf = $socket->_rbuf;
271              
272 0         0 _set_time_remain();
273              
274 0         0 my $n = $socket->sysread($buf, 1024, length($buf));
275 0 0       0 unless ($n) {
276 0         0 die "EOF";
277             }
278 0         0 $socket->_rbuf($buf);
279 0 0       0 if ($buf =~ /\015?\012\015?\012/) {
280             # a whole response present
281 0         0 ($code, $mess, @h) = $socket->read_response_headers(laxed => 1,
282             junk_out => \@junk,
283             );
284 0 0       0 if ($code eq "100") {
285 0         0 $write_wait = 0;
286 0         0 undef($code);
287             }
288             else {
289 0         0 $drop_connection++;
290 0         0 last;
291             # XXX should perhaps try to abort write in a nice way too
292             }
293             }
294             }
295 0 0 0     0 if (defined($wbits) && $wbits =~ /[^\0]/) {
296 0         0 my $n = $socket->syswrite($$wbuf, length($$wbuf), $woffset);
297 0 0       0 unless ($n) {
298 0 0       0 die "syswrite: $!" unless defined $n;
299 0         0 die "syswrite: no bytes written";
300             }
301 0         0 $woffset += $n;
302              
303 0 0 0     0 if (!$eof && $woffset >= length($$wbuf)) {
304             # need to refill buffer from $content_ref code
305 0         0 my $buf = &$content_ref();
306 0 0       0 $buf = "" unless defined($buf);
307 0 0       0 $eof++ unless length($buf);
308 0 0       0 $buf = sprintf "%x%s%s%s", length($buf), $CRLF, $buf, $CRLF
309             if $chunked;
310 0         0 $wbuf = \$buf;
311 0         0 $woffset = 0;
312             }
313             }
314             }
315             }
316              
317 7         24 _set_time_remain();
318 7 50       54 ($code, $mess, @h) = $socket->read_response_headers(laxed => 1, junk_out => \@junk)
319             unless $code;
320 7 50       2109 ($code, $mess, @h) = $socket->read_response_headers(laxed => 1, junk_out => \@junk)
321             if $code eq "100";
322              
323 7         91 my $response = HTTP::Response->new($code, $mess);
324 7         447 my $peer_http_version = $socket->peer_http_version;
325 7         107 $response->protocol("HTTP/$peer_http_version");
326 7         71 while (@h) {
327 26         611 my($k, $v) = splice(@h, 0, 2);
328 26         121 $response->push_header($k, $v);
329             }
330 7 50       175 $response->push_header("Client-Junk" => \@junk) if @junk;
331              
332 7         55 $response->request($request);
333 7         303 $self->_get_sock_info($response, $socket);
334              
335 7 50       768 if ($method eq "CONNECT") {
336 0         0 $response->{client_socket} = $socket; # so it can be picked up
337 0         0 return $response;
338             }
339              
340 7 50       30 if (my @te = $response->remove_header('Transfer-Encoding')) {
341 0         0 $response->push_header('Client-Transfer-Encoding', \@te);
342             }
343 7         215 $response->push_header('Client-Response-Num', $socket->increment_response_count);
344              
345 7         270 my $complete;
346             $response = $self->collect($arg, $response, sub {
347 12     12   12345 my $buf = ""; #prevent use of uninitialized value in SSLeay.xs
348 12         28 my $n;
349             READ:
350             {
351 12         13 _set_time_remain();
  12         28  
352 12         70 $n = $socket->read_entity_body($buf, $size);
353 12 50       459 die "Can't read entity body: $!" unless defined $n;
354 12 50       28 redo READ if $n == -1;
355             }
356 12 100       33 $complete++ if !$n;
357 12         29 return \$buf;
358 7         91 } );
359 7 50       451 $drop_connection++ unless $complete;
360              
361 7         17 _set_time_remain();
362 7         35 @h = $socket->get_trailers;
363 7         1395 while (@h) {
364 0         0 my($k, $v) = splice(@h, 0, 2);
365 0         0 $response->push_header($k, $v);
366             }
367              
368             # keep-alive support
369 7 50       23 unless ($drop_connection) {
370 7 50       28 if (my $conn_cache = $self->{ua}{conn_cache}) {
371 0   0     0 my %connection = map { (lc($_) => 1) }
  0         0  
372             split(/\s*,\s*/, ($response->header("Connection") || ""));
373 0 0 0     0 if (($peer_http_version eq "1.1" && !$connection{close}) ||
      0        
374             $connection{"keep-alive"})
375             {
376 0         0 LWP::Debug::debug("Keep the http connection to $host:$port");
377 0         0 $conn_cache->deposit("http", "$host:$port", $socket);
378             }
379             }
380             }
381              
382 7         801 $response;
383             }
384              
385              
386             #-----------------------------------------------------------
387             package LWPx::Protocol::http_paranoid::SocketMethods;
388              
389             sub sysread {
390 19     19   127 my $self = shift;
391 19         41 my $timeout = $LWPx::Protocol::http_paranoid::TIME_REMAIN;
392              
393 19 50       123 if (defined $timeout) {
394 19 50       42 die "read timeout" unless $self->can_read($timeout);
395             }
396             else {
397             # since we have made the socket non-blocking we
398             # use select to wait for some data to arrive
399 0 0       0 $self->can_read(undef) || die "Assert";
400             }
401 19   100     221 sysread($self, $_[0], $_[1], $_[2] || 0);
402             }
403              
404             sub can_read {
405 38     38   370 my($self, $timeout) = @_;
406 38         69 my $fbits = '';
407 38         133 vec($fbits, fileno($self), 1) = 1;
408 38         67975 my $nfound = select($fbits, undef, undef, $timeout);
409 38 50       159 die "select failed: $!" unless defined $nfound;
410 38         144 return $nfound > 0;
411             }
412              
413             sub ping {
414 0     0   0 my $self = shift;
415 0         0 !$self->can_read(0);
416             }
417              
418             sub increment_response_count {
419 7     7   11 my $self = shift;
420 7         8 return ++${*$self}{'myhttp_response_count'};
  7         41  
421             }
422              
423             #-----------------------------------------------------------
424             package LWPx::Protocol::http_paranoid::Socket;
425 1     1   10 use vars qw(@ISA);
  1         1  
  1         73  
426             @ISA = qw(LWPx::Protocol::http_paranoid::SocketMethods Net::HTTP);
427              
428             1;