File Coverage

blib/lib/File/HTTP.pm
Criterion Covered Total %
statement 78 500 15.6
branch 1 238 0.4
condition 0 149 0.0
subroutine 25 64 39.0
pod 14 19 73.6
total 118 970 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   1558 use strict;
  1         2  
  1         29  
4 1     1   5 use warnings;
  1         2  
  1         22  
5 1     1   9 use Carp;
  1         3  
  1         53  
6 1     1   495 use Symbol ();
  1         831  
  1         22  
7 1     1   599 use Socket ();
  1         3524  
  1         29  
8 1     1   6 use Errno ();
  1         2  
  1         13  
9 1     1   5 use Fcntl ();
  1         2  
  1         15  
10 1     1   4 use Exporter;
  1         2  
  1         31  
11 1     1   529 use bytes ();
  1         14  
  1         27  
12 1     1   506 use Time::HiRes qw(time);
  1         1208  
  1         4  
13 1     1   198 use constant 1.03; # hash ref, perl 5.7.2
  1         24  
  1         209  
14              
15             # on demand modules:
16             # - Time::y2038 or Time::Local
17             # - IO::Socket::SSL
18              
19             our $VERSION = '1.01';
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   411 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         282 goto \&Exporter::import;
40             }
41              
42 1     1   7 use constant DEBUG => 0;
  1         12  
  1         117  
43              
44             # define instance variables
45 1         107 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         1  
78              
79             # build instance variable constants (ala enum::fields)
80 1     1   26 use constant do {my $i=-1; +{ map {$_ => ++$i} FIELDS } };
  1         2  
  1         2  
  1         2  
  1         3  
  28         289  
81              
82             # speed up socket constant calls by making them *really* constant
83 1     1   7 use constant AF_INET => &Socket::AF_INET;
  1         28  
  1         55  
84 1     1   5 use constant SOCK_STREAM => &Socket::SOCK_STREAM;
  1         2  
  1         59  
85 1     1   6 use constant IPPROTO_TCP => &Socket::IPPROTO_TCP;
  1         2  
  1         58  
86 1     1   6 use constant SOL_SOCKET => &Socket::SOL_SOCKET;
  1         1  
  1         59  
87 1     1   6 use constant SO_LINGER => &Socket::SO_LINGER;
  1         2  
  1         81  
88 1     1   7 use constant DONT_LINGER => pack(II => 1, 0);
  1         2  
  1         64  
89 1     1   5 use constant READ_MODE => &Fcntl::S_IRUSR | &Fcntl::S_IRGRP | &Fcntl::S_IROTH;
  1         2  
  1         1929  
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         727  
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         19  
  1         129  
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   6 no warnings;
  1         2  
  1         4089  
  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           my @h = (
487             "GET $self->[PATH] HTTP/1.0",
488             "Host: $self->[NETLOC]",
489             "User-Agent: $USER_AGENT",
490             "Connection: close",
491             );
492             # push @h, "Proxy-Connection: close" if $self->[CONNECT_NETLOC] && $self->[PROTO] ne 'HTTPS';
493 0 0         push @h, "Range: bytes=$self->[OFFSET]-" if defined $self->[OFFSET];
494 0 0         push @h, "Authorization: Basic ". MIME::Base64::encode_base64($self->[AUTH]) if $self->[AUTH];
495            
496 0           join("\015\012", @h, '', '')
497             };
498              
499 0 0         die "error: ".&Errno::EFAULT unless $self->[IP]; # Bad address
500              
501 0 0         if ($self->[FH]) {
502             # shutdown($self->[FH], 2);
503 0           CORE::close($self->[FH]);
504             # select(undef, undef, undef, 0.1);
505             }
506 0           $self->[FH] = undef;
507 0           $self->[REQUEST_TIME] = time;
508 0           ($self->[HTTP_VERSION]) = $REQUEST_HEADERS =~m! HTTP/(\d+\.\d+)\r?\n!;
509 0           $self->[HTTP_VERSION] += 0;
510 0           $self->[LAST_HEADERS_SIZE] = 0;
511 0 0         socket($self->[FH], AF_INET, SOCK_STREAM, IPPROTO_TCP) || die $!;
512             # setsockopt($self->[FH], SOL_SOCKET, SO_LINGER, DONT_LINGER) || die $!;
513              
514 0           select((select($self->[FH]), $|=1)[0]); # autoflush
515 0           for (1..10) {
516 0   0       my $t = $DEBUG_SLOW_CONNECTION && time;
517 0           my $status = connect($self->[FH], Socket::sockaddr_in($self->[PORT], $self->[IP]));
518 0 0 0       if ($DEBUG_SLOW_CONNECTION && time-$t >= .4) {
519 0 0         warn sprintf "\nSLOW %s CONNECTION to %s:%d: %s", ($status ? 'SUCCESS' : 'FAILED'), $self->[HOST], $self->[PORT], time-$t;
520             }
521 0 0         last if $status;
522 0 0 0       die $! unless $_ < 3 && $! =~ /Interrupted system call/i;
523             }
524            
525 0   0       $self->[FH_STAT] ||= [ CORE::stat($self->[FH]) ];
526              
527 0 0         if ($self->[PROTO] eq 'HTTPS') {
528 0           $self->[SSL] = 1;
529 0 0         unless ($SSL_LOADED) {
530 0 0         eval {require IO::Socket::SSL;1} || croak "HTTPS support requires IO::Socket::SSL: $@";
  0            
  0            
531 0           $SSL_LOADED = 1;
532             }
533 0 0         if ($self->[CONNECT_NETLOC]) {
534 0   0       my ($code, $headers) = $self->_handshake(
535             join("\015\012",
536             "CONNECT $self->[CONNECT_NETLOC] HTTP/1.0",
537             "User-Agent: ". ($TUNNELING_USER_AGENT||$USER_AGENT),
538             '',
539             ''
540             )
541             );
542 0 0         die "error: HTTP error $code from proxy during CONNECT\n" unless $code == 200;
543             }
544              
545 0           IO::Socket::SSL->start_SSL($self->[FH],
546             SSL_verifycn_name => $self->[REMOTE_HOST],
547             SSL_hostname => $self->[REMOTE_HOST],
548             SSL_session_cache_size => 100,
549             SSL_verify_mode => &IO::Socket::SSL::SSL_VERIFY_NONE,
550             );
551             }
552              
553 0           (my $code, $RESPONSE_HEADERS) = $self->_handshake($REQUEST_HEADERS);
554              
555 0           $self->[RESPONSE_TIME] = time;
556              
557 0           my $code_ok = do {
558 0 0         if (defined $self->[OFFSET]) {
559 0           $code == 206
560             } else {
561 0 0         $code == 200 || $code == 204
562             }
563             };
564              
565 0 0         if (!$code_ok) {
566 0 0 0       if ($code =~ /^3/ && $RESPONSE_HEADERS =~ /\015?\012Location: ([^\015\012]+)/i) {
    0          
567 0 0         die "redirection: $1\n" unless $IGNORE_REDIRECTIONS;
568             }
569             elsif (!$IGNORE_ERRORS) {
570 0   0       $self->[CONTENT_LENGTH] ||= ($RESPONSE_HEADERS =~ /\015?\012Content-Length: (\d+)/i && $1);
      0        
571 0 0 0       if ($code =~ /^200$|^416$/ && $self->[OFFSET] >= $self->[CONTENT_LENGTH]) {
572 0           DEBUG && warn "out of range\n";
573 0 0         CORE::open($self->[FH] = undef, '<', '/dev/null') || CORE::open($self->[FH] = undef, '<', 'nul');
574             } else {
575 0   0       $! = $HTTP2FS_error{$code} || &Errno::ENOSYS; # ENOSYS: Function not implemented
576 0 0 0       $VERBOSE && $code==200 && carp "Server does not support range queries. Consider using open_stream() instead of open()";
577 0           die "error: ", 0+$!, "\n";
578             }
579             }
580             }
581 0 0 0       if ($RESPONSE_HEADERS =~ m!\015?\012Transfert-Encoding: +chunked!i && $self->[HTTP_VERSION] <= 1) {
582 0   0       $! = $HTTP2FS_error{$code} || &Errno::ENOSYS; # ENOSYS: Function not implemented
583 0           die "error: ", 0+$!, "\n";
584             }
585            
586 0 0         unless (defined $self->[CONTENT_LENGTH]) {
587 0           ($self->[CONTENT_LENGTH]) = $RESPONSE_HEADERS =~ m!\015?\012Content-Range: +bytes +\d*-\d*/(\d+)!i;
588 0 0         unless (defined $self->[CONTENT_LENGTH]) {
589 0           ($self->[CONTENT_LENGTH]) = $RESPONSE_HEADERS =~ m!\015?\012Content-Length: (\d+)!i;
590             }
591             }
592 0 0         unless (defined $self->[CONTENT_TYPE]) {
593 0           ($self->[CONTENT_TYPE]) = $RESPONSE_HEADERS =~ m!\015?\012Content-Type: +([^\015\012]+)!i;
594             }
595 0 0         unless (defined $self->[LAST_MODIFIED]) {
596 0           ($self->[LAST_MODIFIED]) = $RESPONSE_HEADERS =~ m!\015?\012Last-Modified: +([^\015\012]+)!i;
597             }
598            
599 0 0         return unless defined $self->[OFFSET];
600            
601 0           $self->[LAST_READ] = $self->[RESPONSE_TIME];
602 0           $self->[CURRENT_OFFSET] = $self->[OFFSET];
603 0           return 1;
604             }
605              
606             # read() reimplementation to overcome IO::Socket::SSL behavior of read() acting as sysread()
607             # <> is ok though
608             sub _read {
609 0     0     my ($self, undef, $len, $off) = @_;
610            
611 0 0         if (not defined $off) {
    0          
612 0           $off = 0;
613             }
614             elsif ($off < 0) {
615 0           $off += bytes::length($_[1])
616             }
617            
618 0           my $n = read($self->[FH], $_[1], $len, $off);
619 0 0         return $n unless $n;
620            
621 0 0 0       if ($self->[SSL] && $len && $n < $len) {
      0        
622             # strange IO::Socket::SSL behavior: read() acts as sysread()
623 0           while ($n < $len) {
624 0           my $n_part = read($self->[FH], $_[1], $len-$n, $off+$n);
625 0 0         return $n unless $n_part;
626 0           $n += $n_part;
627             }
628             }
629            
630 0           return $n;
631             }
632              
633             sub TIEHANDLE {
634 0     0     my ($class, $url, $offset, $err_ref, $no_close_on_destroy) = @_;
635 0           my $self = bless [], $class;
636 0           my $redirections = 0;
637              
638 0           $self->[NO_CLOSE_ON_DESTROY] = $no_close_on_destroy;
639              
640             SET_URL: {
641 0           $self->[URL] = $url;
  0            
642 0           $self->[OFFSET] = $offset;
643 0           $self->[CURRENT_OFFSET] = $offset;
644 0           ($self->[PROTO], $self->[AUTH], $self->[HOST], $self->[PORT], $self->[PATH]) = $url =~ m!^(https?)://(?:([^/:]+:[^/@]+)@)?([^/:]+)(?:\:(\d+))?(/[^#]+)?!i;
645 0           $self->[REMOTE_HOST] = $self->[HOST];
646              
647 0 0         if ($self->[AUTH]) {
648 0           require MIME::Base64;
649             #$VERBOSE && carp "authentication in URI is not supported";
650             #$$err_ref = &Errno::EFAULT; # Bad address
651             #return undef;
652             }
653 0           $self->[PROTO] = uc($self->[PROTO]);
654 0   0       $self->[PORT] ||= $Proto2Port{$self->[PROTO]};
655 0   0       $self->[PATH] ||= '/';
656 0 0         $self->[NETLOC] = ($self->[PORT]==$Proto2Port{$self->[PROTO]}) ? $self->[HOST] : "$self->[HOST]:$self->[PORT]";
657 0           $self->[CONNECT_NETLOC] = '';
658            
659             # PATH will change in case of proxy
660 0           $self->[REAL_PATH] = $self->[PATH];
661            
662             # handle proxy
663 0 0 0       my $proxy = $self->[PROTO] eq 'HTTPS' ? $ENV{HTTPS_PROXY}||$ENV{HTTP_PROXY} : $ENV{HTTP_PROXY};
664 0 0         if ($proxy) {
665 0   0       my $no_proxy = join('|', map {s/^\*?\.//;$_} split(/[, ]+/, $ENV{NO_PROXY}||''));
  0            
  0            
666            
667 0 0 0       unless (
      0        
      0        
668             ($self->[HOST] eq '127.0.0.1')
669             ||
670             ($self->[HOST] eq 'localhost')
671             ||
672             ($no_proxy && $self->[HOST] =~ /$no_proxy$/i)
673             ) {
674             # apply proxy
675 0 0         if ($proxy =~ m!^https://!) {
676 0 0         $VERBOSE && carp "proxies with HTTPS address are not supported";
677 0           $$err_ref = &Errno::EFAULT; # Bad address
678 0           return undef;
679             }
680 0           $self->[CONNECT_NETLOC] = "$self->[HOST]:$self->[PORT]";
681 0           ($self->[HOST], $self->[PORT]) = $proxy =~ m!^(?:http://)?([^/:]+)(?:\:(\d+))?!i;
682 0   0       $self->[PORT] ||= $Proto2Port{$self->[PROTO]};
683 0           $self->[PATH] = $self->[URL];
684 0           DEBUG && warn "Proxy: $self->[HOST]:$self->[PORT]\n";
685             }
686             }
687              
688 0           $self->[IP] = Socket::inet_aton($self->[HOST]);
689 0           eval { $self->_initiate };
  0            
690              
691 0 0         if ($@) {
692 0 0         if ($@ =~ /^redirection: ([^\n]+)/) {
    0          
    0          
693 0           my $location = $1;
694 0 0         if (++$redirections > $MAX_REDIRECTIONS) {
695 0 0         $VERBOSE && carp "too many redirections";
696 0           $$err_ref = &Errno::EFAULT; # Bad address
697 0           return undef;
698             }
699 0 0         if ($location =~ m!^https?://!i) {
    0          
    0          
700 0           $url = $location;
701             }
702             elsif ($location =~ m!^//!) {
703 0           $url =~ m!^(https?:)//!;
704 0           $url = $1.$location;
705             }
706             elsif ($location =~ m!^/!) {
707 0           $url =~ m!^(https?://[^/]+)!;
708 0           $url = $1.$location;
709             }
710             else {
711 0           $url =~ s!#.*!!;
712 0           $url =~ s![^/]+$!!;
713 0           $url .= $location;
714             }
715 0           redo SET_URL;
716             }
717             elsif ($@ =~ /^error: (\d+)/) {
718 0 0         $VERBOSE && carp $@;
719 0           $$err_ref = $1;
720 0           return undef;
721             }
722             elsif ($@ =~ /^HTTPS support/) {
723 0           die $@;
724             }
725             else {
726 0 0         $VERBOSE && carp $@;
727 0           $$err_ref = &Errno::EIO; # Input/output error
728 0           return undef;
729             }
730             }
731            
732 0 0 0       if (defined($self->[OFFSET]) && not defined $self->[CONTENT_LENGTH]) {
733 0           $$err_ref = &Errno::ENOSYS; # Function not implemented
734 0           return undef;
735             }
736             }
737              
738             $self
739 0           }
740              
741             sub GETC {
742 0     0     my $self = shift;
743 0 0         $self->_initiate || return undef;
744 0           my $n = read($self->[FH], my $buf, 1); # no need for _read(), reading one byte is ok
745 0 0         return unless $n; # eof or error
746 0           ++$self->[OFFSET];
747 0           $self->[CURRENT_OFFSET] = $self->[OFFSET];
748 0           return $buf;
749             }
750              
751             sub READ {
752 0     0     my ($self, undef, $len, $off) = @_;
753 0           my $state = $self->_initiate;
754 0 0         return $state unless $state; # 0 if eof, undef on error
755 0           my $n = $self->_read($_[1], $len, $off);
756 0 0         unless ($n) {
757 0 0         $! = &Errno::EIO if defined $n; # unsuspected close => Input/output error
758 0           return undef;
759             }
760 0           $self->[OFFSET] += $n;
761 0           $self->[CURRENT_OFFSET] = $self->[OFFSET];
762 0           return $n;
763             }
764              
765             sub READLINE {
766 0     0     my $self = shift;
767 0 0         $self->_initiate || return;
768 0           my $fh = $self->[FH];
769 0 0         if (wantarray) {
770 0           $self->[OFFSET] = $self->[CONTENT_LENGTH];
771 0           $self->[CURRENT_OFFSET] = $self->[OFFSET];
772 0           return <$fh>;
773             } else {
774 0           my $line = <$fh>;
775 0           $self->[OFFSET] += bytes::length($line);
776 0           $self->[CURRENT_OFFSET] = $self->[OFFSET];
777 0           return $line;
778             }
779             }
780              
781             sub EOF {
782 0     0     my $self = shift;
783 0 0         defined($self->[CONTENT_LENGTH]) && $self->[OFFSET] >= $self->[CONTENT_LENGTH]
784             }
785              
786             sub TELL {
787 0     0     $_[0]->[OFFSET]
788             }
789              
790             sub SEEK {
791 0     0     my ($self, $offset, $whence) = @_;
792 0 0         unless ($whence) {
    0          
    0          
793 0           $self->[OFFSET] = $offset
794             }
795 0           elsif ($whence == 1) {
796 0           $self->[OFFSET] += $offset
797             }
798 0           elsif ($whence == 2) {
799 0           $self->[OFFSET] = $self->[CONTENT_LENGTH] - $offset
800             }
801             else {
802             return undef
803 0           }
804 0           1
805             }
806              
807             sub WRITE {
808 0     0     croak "Filehandle opened only for input"
809             }
810              
811             sub PRINT {
812 0     0     croak "Filehandle opened only for input"
813             }
814              
815             sub PRINTF {
816 0     0     croak "Filehandle opened only for input"
817             }
818              
819             sub BINMODE {
820 0     0     1
821             }
822              
823             sub CLOSE {
824 0     0     my $self = shift;
825 0 0         return unless $self->[FH];
826             # CORE::shutdown($self->[FH], 2);
827 0           CORE::close($self->[FH]);
828 0           $self->[FH] = undef
829             }
830              
831             sub DESTROY {
832 0     0     DEBUG && warn "\nDESTROY";
833 0   0       my $self = shift || return;
834 0 0         return if $self->[NO_CLOSE_ON_DESTROY];
835 0           $self->CLOSE
836             }
837              
838             # STAT, ISATTY, ISBINARY => used in perl 5.11 ?
839              
840             sub STAT {
841 0     0 0   my $self = shift;
842 0           $self->[FH_STAT]->[3] = READ_MODE;
843 0   0       $self->[FH_STAT]->[7] ||= $self->[CONTENT_LENGTH];
844 0   0       $self->[FH_STAT]->[9] ||= $self->_mtime;
845 0           return @{$self->[FH_STAT]};
  0            
846             }
847              
848             sub _mtime {
849 0     0     my $self = shift;
850 0 0         return $self->[MTIME] if $self->[MTIME];
851 0 0         return 0 unless $self->[LAST_MODIFIED];
852 0 0 0       return 0 unless $TIME_GM_CODE ||= do {
853 0 0         if (eval {require Time::y2038;1}) {
  0 0          
  0            
854 0           \&Time::y2038::timegm
855             }
856 0           elsif (eval {require Time::Local;1}) {
  0            
857 0           \&Time::Local::timegm
858             }
859             };
860 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$/) {
861             # eg: Wed, 11 Jun 2008 12:41:09 GMT
862 0           return $self->[MTIME] = $TIME_GM_CODE->($6, $5, $4, $1, $Mon_str2num{$2}, $3-1900)
863             }
864 0           return 0
865             }
866              
867             sub ISATTY {
868 0     0 0   ''
869             }
870              
871             sub ISBINARY {
872 0     0 0   my $self = shift;
873 0           return $self->[CONTENT_TYPE] !~ m!text/!;
874             }
875              
876             # some other method that might be used
877              
878             sub SIZE {
879 0     0 0   $_[0]->[CONTENT_LENGTH]
880             }
881              
882             sub size {
883 0     0 0   $_[0]->[CONTENT_LENGTH]
884             }
885              
886             1