File Coverage

blib/lib/File/HTTP.pm
Criterion Covered Total %
statement 78 501 15.5
branch 1 240 0.4
condition 0 149 0.0
subroutine 25 64 39.0
pod 14 19 73.6
total 118 973 12.1


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