264
262
return ($scheme, $host, $port, $path_query);
268
HTTPMicro::Handle; # hide from PAUSE/indexers
273
use Errno qw[EINTR EPIPE];
274
use IO::Socket qw[SOCK_STREAM];
276
sub BUFSIZE () { 32768 }
278
my $Printable = sub {
283
s/([^\x20-\x7E])/sprintf('\\x%.2X', ord($1))/ge;
288
my ($class, %args) = @_;
292
max_line_size => 16384,
297
my $ssl_verify_args = {
298
check_cn => "when_only",
299
wildcards_in_alt => "anywhere",
300
wildcards_in_cn => "anywhere"
304
@_ == 4 || croak(q/Usage: $handle->connect(scheme, host, port)/);
305
my ($self, $scheme, $host, $port) = @_;
307
if ( $scheme eq 'https' ) {
308
eval "require IO::Socket::SSL"
309
unless exists $INC{'IO/Socket/SSL.pm'};
310
croak(qq/IO::Socket::SSL must be installed for https support\n/)
311
unless $INC{'IO/Socket/SSL.pm'};
313
elsif ( $scheme ne 'http' ) {
314
croak(qq/Unsupported URL scheme '$scheme'\n/);
317
$self->{fh} = 'IO::Socket::INET'->new(
322
Timeout => $self->{timeout}
323
) or croak(qq/Could not connect to '$host:$port': $@/);
326
or croak(qq/Could not binmode() socket: '$!'/);
328
if ( $scheme eq 'https') {
329
IO::Socket::SSL->start_SSL($self->{fh});
330
ref($self->{fh}) eq 'IO::Socket::SSL'
331
or die(qq/SSL connection failed for $host\n/);
332
if ( $self->{fh}->can("verify_hostname") ) {
333
$self->{fh}->verify_hostname( $host, $ssl_verify_args );
336
my $fh = $self->{fh};
337
_verify_hostname_of_cert($host, _peer_certificate($fh), $ssl_verify_args)
338
or die(qq/SSL certificate not valid for $host\n/);
342
$self->{host} = $host;
343
$self->{port} = $port;
349
@_ == 1 || croak(q/Usage: $handle->close()/);
351
CORE::close($self->{fh})
352
or croak(qq/Could not close socket: '$!'/);
356
@_ == 2 || croak(q/Usage: $handle->write(buf)/);
357
my ($self, $buf) = @_;
359
my $len = length $buf;
362
local $SIG{PIPE} = 'IGNORE';
366
or croak(q/Timed out while waiting for socket to become ready for writing/);
367
my $r = syswrite($self->{fh}, $buf, $len, $off);
371
last unless $len > 0;
373
elsif ($! == EPIPE) {
374
croak(qq/Socket closed by remote server: $!/);
376
elsif ($! != EINTR) {
377
croak(qq/Could not write to socket: '$!'/);
384
@_ == 2 || @_ == 3 || croak(q/Usage: $handle->read(len)/);
385
my ($self, $len) = @_;
388
my $got = length $self->{rbuf};
391
my $take = ($got < $len) ? $got : $len;
392
$buf = substr($self->{rbuf}, 0, $take, '');
398
or croak(q/Timed out while waiting for socket to become ready for reading/);
399
my $r = sysread($self->{fh}, $buf, $len, length $buf);
404
elsif ($! != EINTR) {
405
croak(qq/Could not read from socket: '$!'/);
409
croak(q/Unexpected end of stream/);
415
@_ == 1 || croak(q/Usage: $handle->readline()/);
419
if ($self->{rbuf} =~ s/\A ([^\x0D\x0A]* \x0D?\x0A)//x) {
423
or croak(q/Timed out while waiting for socket to become ready for reading/);
424
my $r = sysread($self->{fh}, $self->{rbuf}, BUFSIZE, length $self->{rbuf});
428
elsif ($! != EINTR) {
429
croak(qq/Could not read from socket: '$!'/);
432
croak(q/Unexpected end of stream while looking for line/);
435
sub read_header_lines {
436
@_ == 1 || @_ == 2 || croak(q/Usage: $handle->read_header_lines([headers])/);
437
my ($self, $headers) = @_;
443
my $line = $self->readline;
445
if ($line =~ /\A ([^\x00-\x1F\x7F:]+) : [\x09\x20]* ([^\x0D\x0A]*)/x) {
446
my ($field_name) = lc $1;
447
$val = \($headers->{$field_name} = $2);
449
elsif ($line =~ /\A [\x09\x20]+ ([^\x0D\x0A]*)/x) {
451
or croak(q/Unexpected header continuation line/);
452
next unless length $1;
453
$$val .= ' ' if length $$val;
456
elsif ($line =~ /\A \x0D?\x0A \z/x) {
460
croak(q/Malformed header line: / . $Printable->($line));
466
sub write_header_lines {
467
(@_ == 2 && ref $_[1] eq 'HASH') || croak(q/Usage: $handle->write_header_lines(headers)/);
468
my($self, $headers) = @_;
471
while (my ($k, $v) = each %$headers) {
472
my $field_name = lc $k;
473
$field_name =~ /\A [\x21\x23-\x27\x2A\x2B\x2D\x2E\x30-\x39\x41-\x5A\x5E-\x7A\x7C\x7E]+ \z/x
474
or croak(q/Invalid HTTP header field name: / . $Printable->($field_name));
475
$field_name =~ s/\b(\w)/\u$1/g;
476
$buf .= "$field_name: $v\x0D\x0A";
479
return $self->write($buf);
482
sub read_content_body {
483
@_ == 3 || @_ == 4 || croak(q/Usage: $handle->read_content_body(callback, response, [read_length])/);
484
my ($self, $cb, $response, $len) = @_;
485
$len ||= $response->{headers}{'content-length'};
487
croak("No content-length in the returned response, and this "
488
. "UA doesn't implement chunking") unless defined $len;
491
my $read = ($len > BUFSIZE) ? BUFSIZE : $len;
492
$cb->($self->read($read), $response);
499
sub write_content_body {
500
@_ == 2 || croak(q/Usage: $handle->write_content_body(request)/);
501
my ($self, $request) = @_;
502
my ($len, $content_length) = (0, $request->{headers}{'content-length'});
504
$len += $self->write($request->{content});
506
$len == $content_length
507
or croak(qq/Content-Length missmatch (got: $len expected: $content_length)/);
512
sub read_response_header {
513
@_ == 1 || croak(q/Usage: $handle->read_response_header()/);
516
my $line = $self->readline;
518
$line =~ /\A (HTTP\/(0*\d+\.0*\d+)) [\x09\x20]+ ([0-9]{3}) [\x09\x20]+ ([^\x0D\x0A]*) \x0D?\x0A/x
519
or croak(q/Malformed Status-Line: / . $Printable->($line));
521
my ($protocol, $version, $status, $reason) = ($1, $2, $3, $4);
526
headers => $self->read_header_lines,
527
protocol => $protocol,
531
sub write_request_header {
532
@_ == 4 || croak(q/Usage: $handle->write_request_header(method, request_uri, headers)/);
533
my ($self, $method, $request_uri, $headers) = @_;
535
return $self->write("$method $request_uri HTTP/1.1\x0D\x0A")
536
+ $self->write_header_lines($headers);
540
my ($self, $type, $timeout) = @_;
541
$timeout = $self->{timeout}
542
unless defined $timeout && $timeout >= 0;
544
my $fd = fileno $self->{fh};
545
defined $fd && $fd >= 0
546
or croak(q/select(2): 'Bad file descriptor'/);
549
my $pending = $timeout;
552
vec(my $fdset = '', $fd, 1) = 1;
555
$nfound = ($type eq 'read')
556
? select($fdset, undef, undef, $pending)
557
: select(undef, $fdset, undef, $pending) ;
560
or croak(qq/select(2): '$!'/);
561
redo if !$timeout || ($pending = $timeout - (time - $initial)) > 0;
571
@_ == 1 || @_ == 2 || croak(q/Usage: $handle->can_read([timeout])/);
573
return $self->_do_timeout('read', @_)
577
@_ == 1 || @_ == 2 || croak(q/Usage: $handle->can_write([timeout])/);
579
return $self->_do_timeout('write', @_)
268
package HTTP::Micro::Handle;
271
use warnings FATAL => 'all';
272
use English qw(-no_match_vars);
275
use Errno qw(EINTR EPIPE);
276
use IO::Socket qw(SOCK_STREAM);
278
sub BUFSIZE () { 32768 }
280
my $Printable = sub {
285
s/([^\x20-\x7E])/sprintf('\\x%.2X', ord($1))/ge;
290
my ($class, %args) = @_;
294
max_line_size => 16384,
299
my $ssl_verify_args = {
300
check_cn => "when_only",
301
wildcards_in_alt => "anywhere",
302
wildcards_in_cn => "anywhere"
306
@_ == 4 || croak(q/Usage: $handle->connect(scheme, host, port)/);
307
my ($self, $scheme, $host, $port) = @_;
309
if ( $scheme eq 'https' ) {
310
eval "require IO::Socket::SSL"
311
unless exists $INC{'IO/Socket/SSL.pm'};
312
croak(qq/IO::Socket::SSL must be installed for https support\n/)
313
unless $INC{'IO/Socket/SSL.pm'};
315
elsif ( $scheme ne 'http' ) {
316
croak(qq/Unsupported URL scheme '$scheme'\n/);
319
$self->{fh} = IO::Socket::INET->new(
324
Timeout => $self->{timeout}
325
) or croak(qq/Could not connect to '$host:$port': $@/);
328
or croak(qq/Could not binmode() socket: '$!'/);
330
if ( $scheme eq 'https') {
331
IO::Socket::SSL->start_SSL($self->{fh});
332
ref($self->{fh}) eq 'IO::Socket::SSL'
333
or die(qq/SSL connection failed for $host\n/);
334
if ( $self->{fh}->can("verify_hostname") ) {
335
$self->{fh}->verify_hostname( $host, $ssl_verify_args );
338
my $fh = $self->{fh};
339
_verify_hostname_of_cert($host, _peer_certificate($fh), $ssl_verify_args)
340
or die(qq/SSL certificate not valid for $host\n/);
344
$self->{host} = $host;
345
$self->{port} = $port;
351
@_ == 1 || croak(q/Usage: $handle->close()/);
353
CORE::close($self->{fh})
354
or croak(qq/Could not close socket: '$!'/);
358
@_ == 2 || croak(q/Usage: $handle->write(buf)/);
359
my ($self, $buf) = @_;
361
my $len = length $buf;
364
local $SIG{PIPE} = 'IGNORE';
368
or croak(q/Timed out while waiting for socket to become ready for writing/);
369
my $r = syswrite($self->{fh}, $buf, $len, $off);
373
last unless $len > 0;
375
elsif ($! == EPIPE) {
376
croak(qq/Socket closed by remote server: $!/);
378
elsif ($! != EINTR) {
379
croak(qq/Could not write to socket: '$!'/);
386
@_ == 2 || @_ == 3 || croak(q/Usage: $handle->read(len)/);
387
my ($self, $len) = @_;
390
my $got = length $self->{rbuf};
393
my $take = ($got < $len) ? $got : $len;
394
$buf = substr($self->{rbuf}, 0, $take, '');
400
or croak(q/Timed out while waiting for socket to become ready for reading/);
401
my $r = sysread($self->{fh}, $buf, $len, length $buf);
406
elsif ($! != EINTR) {
407
croak(qq/Could not read from socket: '$!'/);
411
croak(q/Unexpected end of stream/);
417
@_ == 1 || croak(q/Usage: $handle->readline()/);
421
if ($self->{rbuf} =~ s/\A ([^\x0D\x0A]* \x0D?\x0A)//x) {
425
or croak(q/Timed out while waiting for socket to become ready for reading/);
426
my $r = sysread($self->{fh}, $self->{rbuf}, BUFSIZE, length $self->{rbuf});
430
elsif ($! != EINTR) {
431
croak(qq/Could not read from socket: '$!'/);
434
croak(q/Unexpected end of stream while looking for line/);
437
sub read_header_lines {
438
@_ == 1 || @_ == 2 || croak(q/Usage: $handle->read_header_lines([headers])/);
439
my ($self, $headers) = @_;
445
my $line = $self->readline;
447
if ($line =~ /\A ([^\x00-\x1F\x7F:]+) : [\x09\x20]* ([^\x0D\x0A]*)/x) {
448
my ($field_name) = lc $1;
449
$val = \($headers->{$field_name} = $2);
451
elsif ($line =~ /\A [\x09\x20]+ ([^\x0D\x0A]*)/x) {
453
or croak(q/Unexpected header continuation line/);
454
next unless length $1;
455
$$val .= ' ' if length $$val;
458
elsif ($line =~ /\A \x0D?\x0A \z/x) {
462
croak(q/Malformed header line: / . $Printable->($line));
468
sub write_header_lines {
469
(@_ == 2 && ref $_[1] eq 'HASH') || croak(q/Usage: $handle->write_header_lines(headers)/);
470
my($self, $headers) = @_;
473
while (my ($k, $v) = each %$headers) {
474
my $field_name = lc $k;
475
$field_name =~ /\A [\x21\x23-\x27\x2A\x2B\x2D\x2E\x30-\x39\x41-\x5A\x5E-\x7A\x7C\x7E]+ \z/x
476
or croak(q/Invalid HTTP header field name: / . $Printable->($field_name));
477
$field_name =~ s/\b(\w)/\u$1/g;
478
$buf .= "$field_name: $v\x0D\x0A";
481
return $self->write($buf);
484
sub read_content_body {
485
@_ == 3 || @_ == 4 || croak(q/Usage: $handle->read_content_body(callback, response, [read_length])/);
486
my ($self, $cb, $response, $len) = @_;
487
$len ||= $response->{headers}{'content-length'};
489
croak("No content-length in the returned response, and this "
490
. "UA doesn't implement chunking") unless defined $len;
493
my $read = ($len > BUFSIZE) ? BUFSIZE : $len;
494
$cb->($self->read($read), $response);
501
sub write_content_body {
502
@_ == 2 || croak(q/Usage: $handle->write_content_body(request)/);
503
my ($self, $request) = @_;
504
my ($len, $content_length) = (0, $request->{headers}{'content-length'});
506
$len += $self->write($request->{content});
508
$len == $content_length
509
or croak(qq/Content-Length missmatch (got: $len expected: $content_length)/);
514
sub read_response_header {
515
@_ == 1 || croak(q/Usage: $handle->read_response_header()/);
518
my $line = $self->readline;
520
$line =~ /\A (HTTP\/(0*\d+\.0*\d+)) [\x09\x20]+ ([0-9]{3}) [\x09\x20]+ ([^\x0D\x0A]*) \x0D?\x0A/x
521
or croak(q/Malformed Status-Line: / . $Printable->($line));
523
my ($protocol, $version, $status, $reason) = ($1, $2, $3, $4);
528
headers => $self->read_header_lines,
529
protocol => $protocol,
533
sub write_request_header {
534
@_ == 4 || croak(q/Usage: $handle->write_request_header(method, request_uri, headers)/);
535
my ($self, $method, $request_uri, $headers) = @_;
537
return $self->write("$method $request_uri HTTP/1.1\x0D\x0A")
538
+ $self->write_header_lines($headers);
542
my ($self, $type, $timeout) = @_;
543
$timeout = $self->{timeout}
544
unless defined $timeout && $timeout >= 0;
546
my $fd = fileno $self->{fh};
547
defined $fd && $fd >= 0
548
or croak(q/select(2): 'Bad file descriptor'/);
551
my $pending = $timeout;
554
vec(my $fdset = '', $fd, 1) = 1;
557
$nfound = ($type eq 'read')
558
? select($fdset, undef, undef, $pending)
559
: select(undef, $fdset, undef, $pending) ;
562
or croak(qq/select(2): '$!'/);
563
redo if !$timeout || ($pending = $timeout - (time - $initial)) > 0;
573
@_ == 1 || @_ == 2 || croak(q/Usage: $handle->can_read([timeout])/);
575
return $self->_do_timeout('read', @_)
579
@_ == 1 || @_ == 2 || croak(q/Usage: $handle->can_write([timeout])/);
581
return $self->_do_timeout('write', @_)
583
} # HTTP::Micro::Handle
582
585
my $prog = <<'EOP';