File Coverage

blib/lib/File/HTTP.pm
Criterion Covered Total %
statement 78 479 16.2
branch 1 226 0.4
condition 0 161 0.0
subroutine 25 64 39.0
pod 14 19 73.6
total 118 949 12.4


line stmt bran cond sub pod time code
1             # open a filehanlde to an HTTP URL and read it as if it was a seekable file
2             package File::HTTP;
3 1     1   1879 use strict;
  1         2  
  1         33  
4 1     1   5 use warnings;
  1         2  
  1         32  
5 1     1   5 use Carp;
  1         2  
  1         74  
6 1     1   582 use Symbol ();
  1         974  
  1         26  
7 1     1   670 use Socket ();
  1         4294  
  1         43  
8 1     1   12 use Errno ();
  1         3  
  1         16  
9 1     1   6 use Fcntl ();
  1         2  
  1         17  
10 1     1   5 use Exporter;
  1         3  
  1         46  
11 1     1   1112 use bytes ();
  1         17  
  1         29  
12 1     1   565 use Time::HiRes qw(time);
  1         1492  
  1         6  
13 1     1   234 use constant 1.03; # hash ref, perl 5.7.2
  1         24  
  1         262  
14              
15             # on demand modules:
16             # - Time::y2038 or Time::Local
17             # - IO::Socket::SSL
18              
19             our $VERSION = '1.00';
20              
21             our @EXPORT_OK = qw(
22             open stat open_at open_stream slurp_stream get post
23             opendir readdir rewinddir telldir seekdir closedir
24             opendir_slash
25             _e _s
26             );
27              
28             our %EXPORT_TAGS = (
29             all => \@EXPORT_OK,
30             open => [qw(open stat _s _e)],
31             opendir => [qw(opendir readdir rewinddir telldir seekdir closedir)],
32             );
33              
34             sub import {
35 1 50   1   589 if (grep {$_ eq '-everywhere'} @_) {
  2         11  
36 0         0 @_ = grep {$_ ne '-everywhere'} @_;
  0         0  
37 0         0 eval join(';', map {"*CORE::GLOBAL::$_ = \\&File::HTTP::$_"} qw(open stat opendir readdir rewinddir telldir seekdir closedir));
  0         0  
38             }
39 1         388 goto \&Exporter::import;
40             }
41              
42 1     1   7 use constant DEBUG => 0;
  1         11  
  1         123  
43              
44             # define instance variables
45 1         116 use constant FIELDS => qw(
46             URL
47             PROTO
48             HOST
49             REMOTE_HOST
50             OFFSET
51             CURRENT_OFFSET
52             CONTENT_LENGTH
53             PORT
54             PATH
55             REAL_PATH
56             IP
57             NETLOC
58             CONNECT_NETLOC
59             MTIME
60             LAST_MODIFIED
61             CONTENT_TYPE
62             HTTP_VERSION
63             FH
64             FH_STAT
65             LAST_READ
66             AUTH
67             LAST_HEADERS_SIZE
68             SSL
69            
70             REQUEST_TIME
71             RESPONSE_TIME
72              
73             NO_CLOSE_ON_DESTROY
74            
75             DIR_LIST
76             DIR_POS
77 1     1   10 );
  1         2  
78              
79             # build instance variable constants (ala enum::fields)
80 1     1   7 use constant do {my $i=-1; +{ map {$_ => ++$i} FIELDS } };
  1         2  
  1         2  
  1         2  
  1         3  
  28         314  
81              
82             # speed up socket constant calls by making them *really* constant
83 1     1   8 use constant AF_INET => &Socket::AF_INET;
  1         28  
  1         58  
84 1     1   6 use constant SOCK_STREAM => &Socket::SOCK_STREAM;
  1         1  
  1         71  
85 1     1   6 use constant IPPROTO_TCP => &Socket::IPPROTO_TCP;
  1         3  
  1         66  
86 1     1   6 use constant SOL_SOCKET => &Socket::SOL_SOCKET;
  1         2  
  1         62  
87 1     1   6 use constant SO_LINGER => &Socket::SO_LINGER;
  1         2  
  1         63  
88 1     1   7 use constant DONT_LINGER => pack(II => 1, 0);
  1         1  
  1         68  
89 1     1   7 use constant READ_MODE => &Fcntl::S_IRUSR | &Fcntl::S_IRGRP | &Fcntl::S_IROTH;
  1         1  
  1         1949  
90              
91             # user modifiable global parameters
92             our $REQUEST_HEADERS;
93             our $RESPONSE_HEADERS;
94             our $IGNORE_REDIRECTIONS;
95             our $IGNORE_ERRORS;
96             our $VERBOSE;
97             our $DEBUG_SLOW_CONNECTION;
98             our $MAX_REDIRECTIONS = 7;
99             our $MAX_HEADER_LINES = 50;
100             our $MAX_HEADER_SIZE = 65536;
101             our $MAX_SEC_NO_CLOSE = 3;
102             our $MAX_LENGTH_SKIP = 256*1024;
103             our $USER_AGENT = __PACKAGE__. '/'. $VERSION;
104             our $TUNNELING_USER_AGENT; # default to $USER_AGENT when undefined
105              
106             if (DEBUG) {
107             $VERBOSE = 1;
108             $DEBUG_SLOW_CONNECTION = 1;
109             }
110              
111             my $SSL_LOADED;
112             my $TIME_GM_CODE;
113              
114             my %Mon_str2num = do {
115             my $i=-1;
116             map {$_ => ++$i} qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec)
117             };
118              
119             #for (keys %!) {
120             # $! = Errno->$_;
121             # print "$_ : ", 0+$!, " $!\n";
122             #}
123             #exit;
124              
125             my %HTTP2FS_error = (
126             # No such file or directory
127             404 => &Errno::ENOENT,
128             410 => &Errno::ENOENT,
129             503 => &Errno::ENOENT,
130              
131             # Permission denied
132             401 => &Errno::EACCES,
133             402 => &Errno::EACCES,
134             403 => &Errno::EACCES,
135              
136             # Function not implemented
137             200 => &Errno::ENOSYS,
138             );
139              
140             my %Proto2Port = (
141             HTTP => 80,
142             HTTPS => 443,
143             );
144              
145             sub stat ($) {
146 0     0 1   my $arg = shift;
147 0 0 0       if (defined($arg) && ref($arg)) {
    0          
148 0 0         if ($arg->isa('File::HTTP')) {
    0          
149 0           $arg->STAT
150             }
151             elsif (my $self = tied(*$arg)) {
152 0           $self->STAT
153             }
154             else {
155 0           CORE::stat($arg)
156             }
157             }
158             elsif ($arg =~ m!^https?://!i) {
159 0   0       (TIEHANDLE(__PACKAGE__, $arg, 0) || return undef)->STAT
160             }
161             else {
162 0           CORE::stat($arg)
163             }
164             }
165              
166             sub _s ($) {
167 0     0     [ File::HTTP::stat $_[0] ]->[7]
168             }
169              
170             sub _e ($) {
171 0     0     defined _s($_[0])
172             }
173              
174             sub opendir_slash ($$) {
175 0     0 1   my $dir = pop;
176            
177 0 0 0       if (($dir||'') =~ m!^https?://!) {
178 0   0       $_[0] ||= Symbol::gensym();
179 0   0       my $self = tie(*{$_[0]}, __PACKAGE__, $dir, undef) || return;
180            
181 0           my $path = $self->[REAL_PATH];
182 0           $path =~ s/\?.*$//;
183            
184 0           my $fh = $self->[FH];
185            
186 0           local $/;
187 0           $self->[DIR_LIST] = [ '.', '..', grep {not m!^\.\.?/?!} <$fh> =~ m! href="(?:(?:$self->[PROTO]://)?$path)?([^/\?"]+/?)"!g ];
  0            
188 0           $self->[DIR_POS] = 0;
189 0           1
190             } else {
191 0           CORE::opendir($_[0], $dir)
192             }
193             }
194              
195             sub opendir ($$) {
196 0     0 1   my $dir = pop;
197            
198 0 0 0       if (($dir||'') =~ m!^https?://!) {
199 0   0       $_[0] ||= Symbol::gensym();
200 0   0       my $self = tie(*{$_[0]}, __PACKAGE__, $dir, undef) || return;
201            
202 0           my $path = $self->[REAL_PATH];
203 0           $path =~ s/\?.*$//;
204            
205 0           my $fh = $self->[FH];
206            
207 0           local $/;
208 0           $self->[DIR_LIST] = [ '.', '..', grep {not m!^\.\.?/?!} <$fh> =~ m! href="(?:(?:$self->[PROTO]://)?$path)?([^/\?"]+)/?"!g ];
  0            
209 0           $self->[DIR_POS] = 0;
210 0           1
211             } else {
212 0           CORE::opendir($_[0], $dir)
213             }
214             }
215              
216             sub readdir ($) {
217 0     0 1   my $dirh = shift;
218 0   0       my $self = tied(*$dirh) || return CORE::readdir($dirh);
219 0 0         unless($self->[DIR_LIST]) {
220 0           $! = &Errno::ENOSYS; # XXX should be 'Inappropriate ioctl for device'
221             return
222 0           }
223            
224 0 0         if (wantarray) {
225 0 0         if ($self->[DIR_POS]) {
226 0           @{$self->[DIR_LIST]}[$self->[DIR_POS]..$#{$self->[DIR_LIST]}];
  0            
  0            
227             } else {
228 0           @{$self->[DIR_LIST]}
  0            
229             }
230             } else {
231 0           $self->[DIR_LIST]->[$self->[DIR_POS]++];
232             }
233             }
234              
235             sub rewinddir ($) {
236 0     0 1   my $dirh = shift;
237 0   0       my $self = tied(*$dirh) || return CORE::rewinddir($dirh);
238 0 0         unless($self->[DIR_LIST]) {
239 0           $! = &Errno::ENOSYS; # XXX should be 'Inappropriate ioctl for device'
240             return
241 0           }
242 0           $self->[DIR_POS] = 0;
243 0           1
244             }
245              
246             sub telldir ($) {
247 0     0 1   my $dirh = shift;
248 0   0       my $self = tied(*$dirh) || return CORE::telldir($dirh);
249 0 0         unless($self->[DIR_LIST]) {
250 0           $! = &Errno::ENOSYS; # XXX should be 'Inappropriate ioctl for device'
251             return
252 0           }
253 0           $self->[DIR_POS]
254             }
255              
256             sub seekdir ($$) {
257 0     0 1   my ($dirh, $pos) = @_;
258 0   0       my $self = tied(*$dirh) || return CORE::seekdir($dirh, $pos);
259 0 0         unless($self->[DIR_LIST]) {
260 0           $! = &Errno::ENOSYS; # XXX should be 'Inappropriate ioctl for device'
261             return
262 0           }
263 0           $self->[DIR_POS] = $pos;
264 0           1
265             }
266              
267             sub closedir ($) {
268 0     0 1   my $dirh = shift;
269 0   0       my $self = tied(*$dirh) || return CORE::closedir($dirh);
270 0 0         unless($self->[DIR_LIST]) {
271 0           $! = &Errno::ENOSYS; # XXX should be 'Inappropriate ioctl for device'
272             return
273 0           }
274 0           $self->[FH] = undef;
275 0           $self->[DIR_LIST] = undef;
276 0           $self->[DIR_POS] = undef;
277             }
278              
279             sub open ($;$$) {
280 0 0   0 1   return CORE::open($_[0]) if @_==1;
281 0           my $file = pop;
282 0           my $mode;
283            
284 0 0         if (@_==2) {
    0          
285 0           $mode = pop;
286             }
287             elsif ($file =~ s/^([+<>|]+)\s*//) {
288 0           $mode = $1;
289             }
290             else {
291 0           $mode = '<';
292             }
293            
294 0 0 0       if (($file||'') =~ m!^https?://!) {
295 0 0         if ($mode =~ /^\s*<(?:\s*\:raw)?\s*$/) {
    0          
296 0   0       $_[0] ||= Symbol::gensym();
297 0   0       return tie(*{$_[0]}, __PACKAGE__, $file, 0) && 1
298             }
299             elsif ($mode =~ /<|\+/) {
300 0           $! = &Errno::EROFS; # Read-only file system
301 0           return undef;
302             }
303             else {
304             # pipes, layers other than raw, and anything else is invalid
305 0           $! = &Errno::EINVAL; # Invalid argument
306             return undef
307 0           }
308             } else {
309 0           CORE::open($_[0], $mode, $file)
310             }
311             }
312              
313             sub open_at ($$;$) {
314 0     0 1   my (undef, $file, $offset) = @_;
315 0   0       $offset ||= 0; # no undef
316              
317 0 0 0       if (($file||'') =~ m!^https?://!) {
318 0   0       $_[0] ||= Symbol::gensym();
319 0   0       return tie(*{$_[0]}, __PACKAGE__, $file, $offset) && 1
320             } else {
321 0           CORE::open($_[0], '<', $file);
322 1     1   14 no warnings;
  1         3  
  1         717  
323 0 0 0       seek($_[0], $offset, 0) if $offset && $_[0];
324 0           return $_[0];
325             }
326             }
327              
328             sub open_stream ($;$) {
329 0     0 1   my ($url, $offset) = @_;
330 0 0         $url = "http://$url" unless $url =~ m!^https?://!i;
331 0   0       my $self = TIEHANDLE(__PACKAGE__, $url, $offset, 1) || return undef;
332 0           @$self[CONTENT_LENGTH, FH]
333             }
334              
335             sub slurp_stream {
336 0     0 1   my $url = shift;
337 0   0       my $fh = open_stream($url) || return;
338 0 0         if (wantarray) {
339             <$fh>
340 0           } else {
341 0           local $/;
342             <$fh>
343 0           }
344             }
345              
346             sub get {
347             # args: url, follow redirections
348 0     0 1   my $url = shift;
349 0           local $IGNORE_REDIRECTIONS = not shift;
350 0           local $IGNORE_ERRORS = 1;
351 0           local $REQUEST_HEADERS;
352 0           local $RESPONSE_HEADERS;
353 0           my $fh = open_stream($url);
354             return (
355             $REQUEST_HEADERS,
356             $RESPONSE_HEADERS || "HTTP/1.0 502 Bad Gateway\015\012Content-Length: 0\015\012\015\012",
357 0 0 0       $fh ? do {local $/; <$fh>} : ''
  0            
  0            
358             )
359             }
360              
361             sub post {
362             # args: url, type, body
363             # does not follow redirections
364 0     0 1   my $url = shift;
365 0           my $type = shift;
366              
367 0           my ($proto, undef, $host, $port, $path) = $url =~ m!^(https?)://(?:([^/:]+:[^/@]+)@)?([^/:]+)(?:\:(\d+))?(/[^#]+)?!i;
368              
369 0           $proto = uc $proto;
370 0   0       $port ||= $Proto2Port{$proto};
371 0   0       $path ||= '/';
372 0 0         my $netloc = ($port==$Proto2Port{$proto}) ? $host : "$host:$port";
373              
374 0           local $IGNORE_REDIRECTIONS = 1;
375 0           local $IGNORE_ERRORS = 1;
376 0           local $RESPONSE_HEADERS;
377 0 0         local $REQUEST_HEADERS = \join("\015\012",
378             "POST $path HTTP/1.0",
379             "Host: $netloc",
380             "User-Agent: $USER_AGENT",
381             ($type ? ("Content-Type: $type") : ()),
382             'Content-Length: '. bytes::length($_[0]),
383             "Connection: close",
384             '',
385             $_[0]
386             );
387 0           my $fh = open_stream($url);
388             return (
389             $REQUEST_HEADERS,
390             $RESPONSE_HEADERS || "HTTP/1.0 502 Bad Gateway\015\012Content-Length: 0\015\012\015\012",
391 0 0 0       $fh ? do {local $/; <$fh>} : ''
  0            
  0            
392             )
393             }
394              
395             sub _connected {
396 0     0     my $self = shift;
397 1     1   9 no warnings;
  1         4  
  1         131  
398 0   0       return $self->[FH] && time - $self->[LAST_READ] <= $MAX_SEC_NO_CLOSE;
399             }
400              
401             sub _handshake {
402 0     0     my ($self, $req_headers) = @_;
403              
404 0           my $fh = $self->[FH];
405 0           DEBUG && warn $req_headers;
406 0           my $headers;
407             {
408 1     1   89 no warnings;
  1         2  
  1         4420  
  0            
409 0 0         print($fh $req_headers) || die "error: ".&Errno::EIO."\nwhen sending request:\n$req_headers"; # Input/output error;
410             # shutdown($fh, 1);
411 0 0         $self->_read($headers, 5) || die "error: ".&Errno::EIO."\nwhen reading response headers from request:\n$req_headers"; # Input/output error;
412             }
413 0 0 0       unless (defined($headers) && $headers eq 'HTTP/') {
414 0           die "error: wrong HTTP headers\n";
415             }
416 0           local $/ = "\n";
417 0           $headers .= <$fh>; # first line complete
418 0 0 0       if ($headers !~ m!^HTTP/[\d\.]+ (\d+)! or bytes::length($headers) > $MAX_HEADER_SIZE) {
419 0           die "error: wrong HTTP headers\n"
420             }
421 0           my $code = $1;
422 0           my $nb_lines = 1;
423 0           for (;;) {
424 0           my $line = <$fh>;
425 0 0         die "error: wrong HTTP headers\n" unless defined $line;
426 0           $headers .= $line;
427 0 0         last unless $line =~ /\S/;
428 0 0 0       if (++$nb_lines > $MAX_HEADER_LINES or bytes::length($headers) > $MAX_HEADER_SIZE) {
429 0           die "error: HTTP headers too long\n"
430             }
431             }
432 0           $self->[LAST_HEADERS_SIZE] += bytes::length($headers);
433 0           DEBUG && warn $headers;
434 0           DEBUG && warn time - $self->[REQUEST_TIME];
435            
436 0           return ($code, $headers);
437             }
438              
439             sub _initiate {
440 0     0     my $self = shift;
441 0 0         return 0 if $self->EOF;
442 0   0       $self->[LAST_HEADERS_SIZE] ||= 0;
443 0 0         if ($self->_connected) {
444 0 0 0       if ($self->[CURRENT_OFFSET] == $self->[OFFSET]) {
    0          
445 0           DEBUG && print STDERR "[same offset]";
446 0           $self->[LAST_READ] = time;
447 0           return 1;
448             }
449             elsif ($self->[OFFSET] > $self->[CURRENT_OFFSET] && $self->[OFFSET]-$self->[CURRENT_OFFSET] < $MAX_LENGTH_SKIP+$self->[LAST_HEADERS_SIZE]) {
450 0           DEBUG && warn "skip\n";
451 0           my $to_skip = $self->[OFFSET]-$self->[CURRENT_OFFSET];
452 0 0         $self->_read(my $buf, $to_skip)==$to_skip or return;
453 0           $self->[CURRENT_OFFSET] = $self->[OFFSET];
454 0           $self->[LAST_READ] = time;
455 0           return 1;
456             }
457 0           DEBUG && print STDERR "[close]";
458             }
459 0           elsif (DEBUG) {
460             warn "not connected";
461             }
462              
463 0 0 0       $REQUEST_HEADERS = ($REQUEST_HEADERS && ref $REQUEST_HEADERS) ? $$REQUEST_HEADERS : do {
464 0           my @h = (
465             "GET $self->[PATH] HTTP/1.0",
466             "Host: $self->[NETLOC]",
467             "User-Agent: $USER_AGENT",
468             "Connection: close",
469             );
470             # push @h, "Proxy-Connection: close" if $self->[CONNECT_NETLOC] && $self->[PROTO] ne 'HTTPS';
471 0 0         push @h, "Range: bytes=$self->[OFFSET]-" if defined $self->[OFFSET];
472 0 0         push @h, "Authorization: Basic ". MIME::Base64::encode_base64($self->[AUTH]) if $self->[AUTH];
473            
474 0           join("\015\012", @h, '', '')
475             };
476              
477 0 0         die "error: ".&Errno::EFAULT unless $self->[IP]; # Bad address
478              
479 0 0         if ($self->[FH]) {
480             # shutdown($self->[FH], 2);
481 0           CORE::close($self->[FH]);
482             # select(undef, undef, undef, 0.1);
483             }
484 0           $self->[FH] = undef;
485 0           $self->[REQUEST_TIME] = time;
486 0           ($self->[HTTP_VERSION]) = $REQUEST_HEADERS =~m! HTTP/(\d+\.\d+)\r?\n!;
487 0           $self->[HTTP_VERSION] += 0;
488 0           $self->[LAST_HEADERS_SIZE] = 0;
489 0 0         socket($self->[FH], AF_INET, SOCK_STREAM, IPPROTO_TCP) || die $!;
490             # setsockopt($self->[FH], SOL_SOCKET, SO_LINGER, DONT_LINGER) || die $!;
491              
492 0           select((select($self->[FH]), $|=1)[0]); # autoflush
493 0           for (1..10) {
494 0   0       my $t = $DEBUG_SLOW_CONNECTION && time;
495 0           my $status = connect($self->[FH], Socket::sockaddr_in($self->[PORT], $self->[IP]));
496 0 0 0       if ($DEBUG_SLOW_CONNECTION && time-$t >= .4) {
497 0 0         warn sprintf "\nSLOW %s CONNECTION to %s:%d: %s", ($status ? 'SUCCESS' : 'FAILED'), $self->[HOST], $self->[PORT], time-$t;
498             }
499 0 0         last if $status;
500 0 0 0       die $! unless $_ < 3 && $! =~ /Interrupted system call/i;
501             }
502            
503 0   0       $self->[FH_STAT] ||= [ CORE::stat($self->[FH]) ];
504              
505 0 0         if ($self->[PROTO] eq 'HTTPS') {
506 0           $self->[SSL] = 1;
507 0 0         unless ($SSL_LOADED) {
508 0 0         eval {require IO::Socket::SSL;1} || croak "HTTPS support requires IO::Socket::SSL: $@";
  0            
  0            
509 0           $SSL_LOADED = 1;
510             }
511 0 0         if ($self->[CONNECT_NETLOC]) {
512 0   0       my ($code, $headers) = $self->_handshake(
513             join("\015\012",
514             "CONNECT $self->[CONNECT_NETLOC] HTTP/1.0",
515             "User-Agent: ". ($TUNNELING_USER_AGENT||$USER_AGENT),
516             '',
517             ''
518             )
519             );
520 0 0         die "error: HTTP error $code from proxy during CONNECT\n" unless $code == 200;
521             }
522              
523 0           IO::Socket::SSL->start_SSL($self->[FH],
524             SSL_verifycn_name => $self->[REMOTE_HOST],
525             SSL_hostname => $self->[REMOTE_HOST],
526             SSL_session_cache_size => 100,
527             SSL_verify_mode => &IO::Socket::SSL::SSL_VERIFY_NONE,
528             );
529             }
530              
531 0           (my $code, $RESPONSE_HEADERS) = $self->_handshake($REQUEST_HEADERS);
532              
533 0           $self->[RESPONSE_TIME] = time;
534              
535 0           my $code_ok = do {
536 0 0         if (defined $self->[OFFSET]) {
537 0           $code == 206
538             } else {
539 0 0         $code == 200 || $code == 204
540             }
541             };
542              
543 0 0         if (!$code_ok) {
544 0 0 0       if ($code =~ /^3/ && $RESPONSE_HEADERS =~ /\015?\012Location: ([^\015\012]+)/i) {
    0          
545 0 0         die "redirection: $1\n" unless $IGNORE_REDIRECTIONS;
546             }
547             elsif (!$IGNORE_ERRORS) {
548 0   0       $self->[CONTENT_LENGTH] ||= ($RESPONSE_HEADERS =~ /\015?\012Content-Length: (\d+)/i && $1);
      0        
549 0 0 0       if ($code =~ /^200$|^416$/ && $self->[OFFSET] >= $self->[CONTENT_LENGTH]) {
550 0           DEBUG && warn "out of range\n";
551 0 0         CORE::open($self->[FH] = undef, '<', '/dev/null') || CORE::open($self->[FH] = undef, '<', 'nul');
552             } else {
553 0   0       $! = $HTTP2FS_error{$code} || &Errno::ENOSYS; # ENOSYS: Function not implemented
554 0 0 0       $VERBOSE && $code==200 && carp "Server does not support range queries. Consider using open_stream() instead of open()";
555 0           die "error: $!\n";
556             }
557             }
558             }
559 0 0 0       if ($RESPONSE_HEADERS =~ m!\015?\012Transfert-Encoding: +chunked!i && $self->[HTTP_VERSION] <= 1) {
560 0   0       $! = $HTTP2FS_error{$code} || &Errno::ENOSYS; # ENOSYS: Function not implemented
561 0           die "error: $!\n";
562             }
563            
564 0 0         unless (defined $self->[CONTENT_LENGTH]) {
565 0           ($self->[CONTENT_LENGTH]) = $RESPONSE_HEADERS =~ m!\015?\012Content-Range: +bytes +\d*-\d*/(\d+)!i;
566 0 0         unless (defined $self->[CONTENT_LENGTH]) {
567 0           ($self->[CONTENT_LENGTH]) = $RESPONSE_HEADERS =~ m!\015?\012Content-Length: (\d+)!i;
568             }
569             }
570 0 0         unless (defined $self->[CONTENT_TYPE]) {
571 0           ($self->[CONTENT_TYPE]) = $RESPONSE_HEADERS =~ m!\015?\012Content-Type: +([^\015\012]+)!i;
572             }
573 0 0         unless (defined $self->[LAST_MODIFIED]) {
574 0           ($self->[LAST_MODIFIED]) = $RESPONSE_HEADERS =~ m!\015?\012Last-Modified: +([^\015\012]+)!i;
575             }
576            
577 0 0         return unless defined $self->[OFFSET];
578            
579 0           $self->[LAST_READ] = $self->[RESPONSE_TIME];
580 0           $self->[CURRENT_OFFSET] = $self->[OFFSET];
581 0           return 1;
582             }
583              
584             # read() reimplementation to overcome IO::Socket::SSL behavior of read() acting as sysread()
585             # <> is ok though
586             sub _read {
587 0     0     my ($self, undef, $len, $off) = @_;
588            
589 0 0         if (not defined $off) {
    0          
590 0           $off = 0;
591             }
592             elsif ($off < 0) {
593 0           $off += bytes::length($_[1])
594             }
595            
596 0           my $n = read($self->[FH], $_[1], $len, $off);
597 0 0         return $n unless $n;
598            
599 0 0 0       if ($self->[SSL] && $len && $n < $len) {
      0        
600             # strange IO::Socket::SSL behavior: read() acts as sysread()
601 0           while ($n < $len) {
602 0           my $n_part = read($self->[FH], $_[1], $len-$n, $off+$n);
603 0 0         return $n unless $n_part;
604 0           $n += $n_part;
605             }
606             }
607            
608 0           return $n;
609             }
610              
611             sub TIEHANDLE {
612 0     0     my ($class, $url, $offset, $no_close_on_destroy) = @_;
613 0           my $self = bless [], $class;
614 0           my $redirections = 0;
615              
616 0           $self->[NO_CLOSE_ON_DESTROY] = $no_close_on_destroy;
617              
618             SET_URL: {
619 0           $self->[URL] = $url;
  0            
620 0           $self->[OFFSET] = $offset;
621 0           $self->[CURRENT_OFFSET] = $offset;
622 0           ($self->[PROTO], $self->[AUTH], $self->[HOST], $self->[PORT], $self->[PATH]) = $url =~ m!^(https?)://(?:([^/:]+:[^/@]+)@)?([^/:]+)(?:\:(\d+))?(/[^#]+)?!i;
623 0           $self->[REMOTE_HOST] = $self->[HOST];
624              
625 0 0         if ($self->[AUTH]) {
626 0           require MIME::Base64;
627             #$VERBOSE && carp "authentication in URI is not supported";
628             #$! = &Errno::EFAULT; # Bad address
629             #return undef;
630             }
631 0           $self->[PROTO] = uc($self->[PROTO]);
632 0   0       $self->[PORT] ||= $Proto2Port{$self->[PROTO]};
633 0   0       $self->[PATH] ||= '/';
634 0 0         $self->[NETLOC] = ($self->[PORT]==$Proto2Port{$self->[PROTO]}) ? $self->[HOST] : "$self->[HOST]:$self->[PORT]";
635 0           $self->[CONNECT_NETLOC] = '';
636            
637             # PATH will change in case of proxy
638 0           $self->[REAL_PATH] = $self->[PATH];
639            
640             # handle proxy
641 0 0 0       my $proxy = $self->[PROTO] eq 'HTTPS' ? $ENV{HTTPS_PROXY}||$ENV{HTTP_PROXY} : $ENV{HTTP_PROXY};
642 0 0         if ($proxy) {
643 0   0       my $no_proxy = join('|', map {s/^\*?\.//;$_} split(/[, ]+/, $ENV{NO_PROXY}||''));
  0            
  0            
644            
645 0 0 0       unless (
      0        
      0        
646             ($self->[HOST] eq '127.0.0.1')
647             ||
648             ($self->[HOST] eq 'localhost')
649             ||
650             ($no_proxy && $self->[HOST] =~ /$no_proxy$/i)
651             ) {
652             # apply proxy
653 0 0         if ($proxy =~ m!^https://!) {
654 0 0         $VERBOSE && carp "proxies with HTTPS address are not supported";
655 0           $! = &Errno::EFAULT; # Bad address
656 0           return undef;
657             }
658 0           $self->[CONNECT_NETLOC] = "$self->[HOST]:$self->[PORT]";
659 0           ($self->[HOST], $self->[PORT]) = $proxy =~ m!^(?:http://)?([^/:]+)(?:\:(\d+))?!i;
660 0   0       $self->[PORT] ||= $Proto2Port{$self->[PROTO]};
661 0           $self->[PATH] = $self->[URL];
662 0           DEBUG && warn "Proxy: $self->[HOST]:$self->[PORT]\n";
663             }
664             }
665              
666 0           $self->[IP] = Socket::inet_aton($self->[HOST]);
667 0           eval { $self->_initiate };
  0            
668              
669 0 0         if ($@) {
670 0 0         if ($@ =~ /^redirection: ([^\n]+)/) {
    0          
    0          
671 0           my $location = $1;
672 0 0         if (++$redirections > $MAX_REDIRECTIONS) {
673 0 0         $VERBOSE && carp "too many redirections";
674 0           $! = &Errno::EFAULT; # Bad address
675 0           return undef;
676             }
677 0 0         if ($location =~ m!^https?://!i) {
    0          
    0          
678 0           $url = $location;
679             }
680             elsif ($location =~ m!^//!) {
681 0           $url =~ m!^(https?:)//!;
682 0           $url = $1.$location;
683             }
684             elsif ($location =~ m!^/!) {
685 0           $url =~ m!^(https?://[^/]+)!;
686 0           $url = $1.$location;
687             }
688             else {
689 0           $url =~ s!#.*!!;
690 0           $url =~ s![^/]+$!!;
691 0           $url .= $location;
692             }
693 0           redo SET_URL;
694             }
695             elsif ($@ =~ /^error: (\d+)/) {
696 0 0         $VERBOSE && carp $@;
697 0           $! = $1;
698 0           return undef;
699             }
700             elsif ($@ =~ /^HTTPS support/) {
701 0           die $@;
702             }
703             else {
704 0 0         $VERBOSE && carp $@;
705 0           $! = &Errno::EIO; # Input/output error
706 0           return undef;
707             }
708             }
709            
710 0 0 0       if (defined($self->[OFFSET]) && not defined $self->[CONTENT_LENGTH]) {
711 0           $! = &Errno::ENOSYS; # Function not implemented
712 0           return undef;
713             }
714             }
715              
716             $self
717 0           }
718              
719             sub GETC {
720 0     0     my $self = shift;
721 0 0         $self->_initiate || return undef;
722 0           my $n = read($self->[FH], my $buf, 1); # no need for _read(), reading one byte is ok
723 0 0         return unless $n; # eof or error
724 0           ++$self->[OFFSET];
725 0           $self->[CURRENT_OFFSET] = $self->[OFFSET];
726 0           return $buf;
727             }
728              
729             sub READ {
730 0     0     my ($self, undef, $len, $off) = @_;
731 0           my $state = $self->_initiate;
732 0 0         return $state unless $state; # 0 if eof, undef on error
733 0           my $n = $self->_read($_[1], $len, $off);
734 0 0         unless ($n) {
735 0 0         $! = &Errno::EIO if defined $n; # unsuspected close => Input/output error
736 0           return undef;
737             }
738 0           $self->[OFFSET] += $n;
739 0           $self->[CURRENT_OFFSET] = $self->[OFFSET];
740 0           return $n;
741             }
742              
743             sub READLINE {
744 0     0     my $self = shift;
745 0 0         $self->_initiate || return;
746 0           my $fh = $self->[FH];
747 0 0         if (wantarray) {
748 0           $self->[OFFSET] = $self->[CONTENT_LENGTH];
749 0           $self->[CURRENT_OFFSET] = $self->[OFFSET];
750 0           return <$fh>;
751             } else {
752 0           my $line = <$fh>;
753 0           $self->[OFFSET] += bytes::length($line);
754 0           $self->[CURRENT_OFFSET] = $self->[OFFSET];
755 0           return $line;
756             }
757             }
758              
759             sub EOF {
760 0     0     my $self = shift;
761 0 0         defined($self->[CONTENT_LENGTH]) && $self->[OFFSET] >= $self->[CONTENT_LENGTH]
762             }
763              
764             sub TELL {
765 0     0     $_[0]->[OFFSET]
766             }
767              
768             sub SEEK {
769 0     0     my ($self, $offset, $whence) = @_;
770 0 0         unless ($whence) {
    0          
    0          
771 0           $self->[OFFSET] = $offset
772             }
773 0           elsif ($whence == 1) {
774 0           $self->[OFFSET] += $offset
775             }
776 0           elsif ($whence == 2) {
777 0           $self->[OFFSET] = $self->[CONTENT_LENGTH] - $offset
778             }
779             else {
780             return undef
781 0           }
782 0           1
783             }
784              
785             sub WRITE {
786 0     0     croak "Filehandle opened only for input"
787             }
788              
789             sub PRINT {
790 0     0     croak "Filehandle opened only for input"
791             }
792              
793             sub PRINTF {
794 0     0     croak "Filehandle opened only for input"
795             }
796              
797             sub BINMODE {
798 0     0     1
799             }
800              
801             sub CLOSE {
802 0     0     my $self = shift;
803 0 0         return unless $self->[FH];
804             # CORE::shutdown($self->[FH], 2);
805 0           CORE::close($self->[FH]);
806 0           $self->[FH] = undef
807             }
808              
809             sub DESTROY {
810 0     0     DEBUG && warn "\nDESTROY";
811 0   0       my $self = shift || return;
812 0 0         return if $self->[NO_CLOSE_ON_DESTROY];
813 0           $self->CLOSE
814             }
815              
816             # STAT, ISATTY, ISBINARY => used in perl 5.11 ?
817              
818             sub STAT {
819 0     0 0   my $self = shift;
820 0           $self->[FH_STAT]->[3] = READ_MODE;
821 0   0       $self->[FH_STAT]->[7] ||= $self->[CONTENT_LENGTH];
822 0   0       $self->[FH_STAT]->[9] ||= $self->_mtime;
823 0           return @{$self->[FH_STAT]};
  0            
824             }
825              
826             sub _mtime {
827 0     0     my $self = shift;
828 0 0         return $self->[MTIME] if $self->[MTIME];
829 0 0         return 0 unless $self->[LAST_MODIFIED];
830 0 0 0       return 0 unless $TIME_GM_CODE ||= do {
831 0 0         if (eval {require Time::y2038;1}) {
  0 0          
  0            
832 0           \&Time::y2038::timegm
833             }
834 0           elsif (eval {require Time::Local;1}) {
  0            
835 0           \&Time::Local::timegm
836             }
837             };
838 0 0         if ($self->[LAST_MODIFIED] =~ /^(?:Sun|Mon|Tue|Wed|Thu|Fri|Sat), (\d{1,2}) (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) (\d{4}) (\d{2}):(\d{2}):(\d{2}) GMT$/) {
839             # eg: Wed, 11 Jun 2008 12:41:09 GMT
840 0           return $self->[MTIME] = $TIME_GM_CODE->($6, $5, $4, $1, $Mon_str2num{$2}, $3-1900)
841             }
842 0           return 0
843             }
844              
845             sub ISATTY {
846 0     0 0   ''
847             }
848              
849             sub ISBINARY {
850 0     0 0   my $self = shift;
851 0           return $self->[CONTENT_TYPE] !~ m!text/!;
852             }
853              
854             # some other method that might be used
855              
856             sub SIZE {
857 0     0 0   $_[0]->[CONTENT_LENGTH]
858             }
859              
860             sub size {
861 0     0 0   $_[0]->[CONTENT_LENGTH]
862             }
863              
864             1