4353
4351
return ($scheme, $host, $port, $path_query);
4357
HTTPMicro::Handle; # hide from PAUSE/indexers
4362
use Errno qw[EINTR EPIPE];
4363
use IO::Socket qw[SOCK_STREAM];
4365
sub BUFSIZE () { 32768 }
4367
my $Printable = sub {
4372
s/([^\x20-\x7E])/sprintf('\\x%.2X', ord($1))/ge;
4377
my ($class, %args) = @_;
4381
max_line_size => 16384,
4386
my $ssl_verify_args = {
4387
check_cn => "when_only",
4388
wildcards_in_alt => "anywhere",
4389
wildcards_in_cn => "anywhere"
4393
@_ == 4 || croak(q/Usage: $handle->connect(scheme, host, port)/);
4394
my ($self, $scheme, $host, $port) = @_;
4396
if ( $scheme eq 'https' ) {
4397
eval "require IO::Socket::SSL"
4398
unless exists $INC{'IO/Socket/SSL.pm'};
4399
croak(qq/IO::Socket::SSL must be installed for https support\n/)
4400
unless $INC{'IO/Socket/SSL.pm'};
4402
elsif ( $scheme ne 'http' ) {
4403
croak(qq/Unsupported URL scheme '$scheme'\n/);
4406
$self->{fh} = 'IO::Socket::INET'->new(
4410
Type => SOCK_STREAM,
4411
Timeout => $self->{timeout}
4412
) or croak(qq/Could not connect to '$host:$port': $@/);
4414
binmode($self->{fh})
4415
or croak(qq/Could not binmode() socket: '$!'/);
4417
if ( $scheme eq 'https') {
4418
IO::Socket::SSL->start_SSL($self->{fh});
4419
ref($self->{fh}) eq 'IO::Socket::SSL'
4420
or die(qq/SSL connection failed for $host\n/);
4421
if ( $self->{fh}->can("verify_hostname") ) {
4422
$self->{fh}->verify_hostname( $host, $ssl_verify_args );
4425
my $fh = $self->{fh};
4426
_verify_hostname_of_cert($host, _peer_certificate($fh), $ssl_verify_args)
4427
or die(qq/SSL certificate not valid for $host\n/);
4431
$self->{host} = $host;
4432
$self->{port} = $port;
4438
@_ == 1 || croak(q/Usage: $handle->close()/);
4440
CORE::close($self->{fh})
4441
or croak(qq/Could not close socket: '$!'/);
4445
@_ == 2 || croak(q/Usage: $handle->write(buf)/);
4446
my ($self, $buf) = @_;
4448
my $len = length $buf;
4451
local $SIG{PIPE} = 'IGNORE';
4455
or croak(q/Timed out while waiting for socket to become ready for writing/);
4456
my $r = syswrite($self->{fh}, $buf, $len, $off);
4460
last unless $len > 0;
4462
elsif ($! == EPIPE) {
4463
croak(qq/Socket closed by remote server: $!/);
4465
elsif ($! != EINTR) {
4466
croak(qq/Could not write to socket: '$!'/);
4473
@_ == 2 || @_ == 3 || croak(q/Usage: $handle->read(len)/);
4474
my ($self, $len) = @_;
4477
my $got = length $self->{rbuf};
4480
my $take = ($got < $len) ? $got : $len;
4481
$buf = substr($self->{rbuf}, 0, $take, '');
4487
or croak(q/Timed out while waiting for socket to become ready for reading/);
4488
my $r = sysread($self->{fh}, $buf, $len, length $buf);
4493
elsif ($! != EINTR) {
4494
croak(qq/Could not read from socket: '$!'/);
4498
croak(q/Unexpected end of stream/);
4504
@_ == 1 || croak(q/Usage: $handle->readline()/);
4508
if ($self->{rbuf} =~ s/\A ([^\x0D\x0A]* \x0D?\x0A)//x) {
4512
or croak(q/Timed out while waiting for socket to become ready for reading/);
4513
my $r = sysread($self->{fh}, $self->{rbuf}, BUFSIZE, length $self->{rbuf});
4517
elsif ($! != EINTR) {
4518
croak(qq/Could not read from socket: '$!'/);
4521
croak(q/Unexpected end of stream while looking for line/);
4524
sub read_header_lines {
4525
@_ == 1 || @_ == 2 || croak(q/Usage: $handle->read_header_lines([headers])/);
4526
my ($self, $headers) = @_;
4532
my $line = $self->readline;
4534
if ($line =~ /\A ([^\x00-\x1F\x7F:]+) : [\x09\x20]* ([^\x0D\x0A]*)/x) {
4535
my ($field_name) = lc $1;
4536
$val = \($headers->{$field_name} = $2);
4538
elsif ($line =~ /\A [\x09\x20]+ ([^\x0D\x0A]*)/x) {
4540
or croak(q/Unexpected header continuation line/);
4541
next unless length $1;
4542
$$val .= ' ' if length $$val;
4545
elsif ($line =~ /\A \x0D?\x0A \z/x) {
4549
croak(q/Malformed header line: / . $Printable->($line));
4555
sub write_header_lines {
4556
(@_ == 2 && ref $_[1] eq 'HASH') || croak(q/Usage: $handle->write_header_lines(headers)/);
4557
my($self, $headers) = @_;
4560
while (my ($k, $v) = each %$headers) {
4561
my $field_name = lc $k;
4562
$field_name =~ /\A [\x21\x23-\x27\x2A\x2B\x2D\x2E\x30-\x39\x41-\x5A\x5E-\x7A\x7C\x7E]+ \z/x
4563
or croak(q/Invalid HTTP header field name: / . $Printable->($field_name));
4564
$field_name =~ s/\b(\w)/\u$1/g;
4565
$buf .= "$field_name: $v\x0D\x0A";
4568
return $self->write($buf);
4571
sub read_content_body {
4572
@_ == 3 || @_ == 4 || croak(q/Usage: $handle->read_content_body(callback, response, [read_length])/);
4573
my ($self, $cb, $response, $len) = @_;
4574
$len ||= $response->{headers}{'content-length'};
4576
croak("No content-length in the returned response, and this "
4577
. "UA doesn't implement chunking") unless defined $len;
4580
my $read = ($len > BUFSIZE) ? BUFSIZE : $len;
4581
$cb->($self->read($read), $response);
4588
sub write_content_body {
4589
@_ == 2 || croak(q/Usage: $handle->write_content_body(request)/);
4590
my ($self, $request) = @_;
4591
my ($len, $content_length) = (0, $request->{headers}{'content-length'});
4593
$len += $self->write($request->{content});
4595
$len == $content_length
4596
or croak(qq/Content-Length missmatch (got: $len expected: $content_length)/);
4601
sub read_response_header {
4602
@_ == 1 || croak(q/Usage: $handle->read_response_header()/);
4605
my $line = $self->readline;
4607
$line =~ /\A (HTTP\/(0*\d+\.0*\d+)) [\x09\x20]+ ([0-9]{3}) [\x09\x20]+ ([^\x0D\x0A]*) \x0D?\x0A/x
4608
or croak(q/Malformed Status-Line: / . $Printable->($line));
4610
my ($protocol, $version, $status, $reason) = ($1, $2, $3, $4);
4615
headers => $self->read_header_lines,
4616
protocol => $protocol,
4620
sub write_request_header {
4621
@_ == 4 || croak(q/Usage: $handle->write_request_header(method, request_uri, headers)/);
4622
my ($self, $method, $request_uri, $headers) = @_;
4624
return $self->write("$method $request_uri HTTP/1.1\x0D\x0A")
4625
+ $self->write_header_lines($headers);
4629
my ($self, $type, $timeout) = @_;
4630
$timeout = $self->{timeout}
4631
unless defined $timeout && $timeout >= 0;
4633
my $fd = fileno $self->{fh};
4634
defined $fd && $fd >= 0
4635
or croak(q/select(2): 'Bad file descriptor'/);
4638
my $pending = $timeout;
4641
vec(my $fdset = '', $fd, 1) = 1;
4644
$nfound = ($type eq 'read')
4645
? select($fdset, undef, undef, $pending)
4646
: select(undef, $fdset, undef, $pending) ;
4647
if ($nfound == -1) {
4649
or croak(qq/select(2): '$!'/);
4650
redo if !$timeout || ($pending = $timeout - (time - $initial)) > 0;
4660
@_ == 1 || @_ == 2 || croak(q/Usage: $handle->can_read([timeout])/);
4662
return $self->_do_timeout('read', @_)
4666
@_ == 1 || @_ == 2 || croak(q/Usage: $handle->can_write([timeout])/);
4668
return $self->_do_timeout('write', @_)
4357
package HTTP::Micro::Handle;
4360
use warnings FATAL => 'all';
4361
use English qw(-no_match_vars);
4364
use Errno qw(EINTR EPIPE);
4365
use IO::Socket qw(SOCK_STREAM);
4367
sub BUFSIZE () { 32768 }
4369
my $Printable = sub {
4374
s/([^\x20-\x7E])/sprintf('\\x%.2X', ord($1))/ge;
4379
my ($class, %args) = @_;
4383
max_line_size => 16384,
4388
my $ssl_verify_args = {
4389
check_cn => "when_only",
4390
wildcards_in_alt => "anywhere",
4391
wildcards_in_cn => "anywhere"
4395
@_ == 4 || croak(q/Usage: $handle->connect(scheme, host, port)/);
4396
my ($self, $scheme, $host, $port) = @_;
4398
if ( $scheme eq 'https' ) {
4399
eval "require IO::Socket::SSL"
4400
unless exists $INC{'IO/Socket/SSL.pm'};
4401
croak(qq/IO::Socket::SSL must be installed for https support\n/)
4402
unless $INC{'IO/Socket/SSL.pm'};
4404
elsif ( $scheme ne 'http' ) {
4405
croak(qq/Unsupported URL scheme '$scheme'\n/);
4408
$self->{fh} = IO::Socket::INET->new(
4412
Type => SOCK_STREAM,
4413
Timeout => $self->{timeout}
4414
) or croak(qq/Could not connect to '$host:$port': $@/);
4416
binmode($self->{fh})
4417
or croak(qq/Could not binmode() socket: '$!'/);
4419
if ( $scheme eq 'https') {
4420
IO::Socket::SSL->start_SSL($self->{fh});
4421
ref($self->{fh}) eq 'IO::Socket::SSL'
4422
or die(qq/SSL connection failed for $host\n/);
4423
if ( $self->{fh}->can("verify_hostname") ) {
4424
$self->{fh}->verify_hostname( $host, $ssl_verify_args );
4427
my $fh = $self->{fh};
4428
_verify_hostname_of_cert($host, _peer_certificate($fh), $ssl_verify_args)
4429
or die(qq/SSL certificate not valid for $host\n/);
4433
$self->{host} = $host;
4434
$self->{port} = $port;
4440
@_ == 1 || croak(q/Usage: $handle->close()/);
4442
CORE::close($self->{fh})
4443
or croak(qq/Could not close socket: '$!'/);
4447
@_ == 2 || croak(q/Usage: $handle->write(buf)/);
4448
my ($self, $buf) = @_;
4450
my $len = length $buf;
4453
local $SIG{PIPE} = 'IGNORE';
4457
or croak(q/Timed out while waiting for socket to become ready for writing/);
4458
my $r = syswrite($self->{fh}, $buf, $len, $off);
4462
last unless $len > 0;
4464
elsif ($! == EPIPE) {
4465
croak(qq/Socket closed by remote server: $!/);
4467
elsif ($! != EINTR) {
4468
croak(qq/Could not write to socket: '$!'/);
4475
@_ == 2 || @_ == 3 || croak(q/Usage: $handle->read(len)/);
4476
my ($self, $len) = @_;
4479
my $got = length $self->{rbuf};
4482
my $take = ($got < $len) ? $got : $len;
4483
$buf = substr($self->{rbuf}, 0, $take, '');
4489
or croak(q/Timed out while waiting for socket to become ready for reading/);
4490
my $r = sysread($self->{fh}, $buf, $len, length $buf);
4495
elsif ($! != EINTR) {
4496
croak(qq/Could not read from socket: '$!'/);
4500
croak(q/Unexpected end of stream/);
4506
@_ == 1 || croak(q/Usage: $handle->readline()/);
4510
if ($self->{rbuf} =~ s/\A ([^\x0D\x0A]* \x0D?\x0A)//x) {
4514
or croak(q/Timed out while waiting for socket to become ready for reading/);
4515
my $r = sysread($self->{fh}, $self->{rbuf}, BUFSIZE, length $self->{rbuf});
4519
elsif ($! != EINTR) {
4520
croak(qq/Could not read from socket: '$!'/);
4523
croak(q/Unexpected end of stream while looking for line/);
4526
sub read_header_lines {
4527
@_ == 1 || @_ == 2 || croak(q/Usage: $handle->read_header_lines([headers])/);
4528
my ($self, $headers) = @_;
4534
my $line = $self->readline;
4536
if ($line =~ /\A ([^\x00-\x1F\x7F:]+) : [\x09\x20]* ([^\x0D\x0A]*)/x) {
4537
my ($field_name) = lc $1;
4538
$val = \($headers->{$field_name} = $2);
4540
elsif ($line =~ /\A [\x09\x20]+ ([^\x0D\x0A]*)/x) {
4542
or croak(q/Unexpected header continuation line/);
4543
next unless length $1;
4544
$$val .= ' ' if length $$val;
4547
elsif ($line =~ /\A \x0D?\x0A \z/x) {
4551
croak(q/Malformed header line: / . $Printable->($line));
4557
sub write_header_lines {
4558
(@_ == 2 && ref $_[1] eq 'HASH') || croak(q/Usage: $handle->write_header_lines(headers)/);
4559
my($self, $headers) = @_;
4562
while (my ($k, $v) = each %$headers) {
4563
my $field_name = lc $k;
4564
$field_name =~ /\A [\x21\x23-\x27\x2A\x2B\x2D\x2E\x30-\x39\x41-\x5A\x5E-\x7A\x7C\x7E]+ \z/x
4565
or croak(q/Invalid HTTP header field name: / . $Printable->($field_name));
4566
$field_name =~ s/\b(\w)/\u$1/g;
4567
$buf .= "$field_name: $v\x0D\x0A";
4570
return $self->write($buf);
4573
sub read_content_body {
4574
@_ == 3 || @_ == 4 || croak(q/Usage: $handle->read_content_body(callback, response, [read_length])/);
4575
my ($self, $cb, $response, $len) = @_;
4576
$len ||= $response->{headers}{'content-length'};
4578
croak("No content-length in the returned response, and this "
4579
. "UA doesn't implement chunking") unless defined $len;
4582
my $read = ($len > BUFSIZE) ? BUFSIZE : $len;
4583
$cb->($self->read($read), $response);
4590
sub write_content_body {
4591
@_ == 2 || croak(q/Usage: $handle->write_content_body(request)/);
4592
my ($self, $request) = @_;
4593
my ($len, $content_length) = (0, $request->{headers}{'content-length'});
4595
$len += $self->write($request->{content});
4597
$len == $content_length
4598
or croak(qq/Content-Length missmatch (got: $len expected: $content_length)/);
4603
sub read_response_header {
4604
@_ == 1 || croak(q/Usage: $handle->read_response_header()/);
4607
my $line = $self->readline;
4609
$line =~ /\A (HTTP\/(0*\d+\.0*\d+)) [\x09\x20]+ ([0-9]{3}) [\x09\x20]+ ([^\x0D\x0A]*) \x0D?\x0A/x
4610
or croak(q/Malformed Status-Line: / . $Printable->($line));
4612
my ($protocol, $version, $status, $reason) = ($1, $2, $3, $4);
4617
headers => $self->read_header_lines,
4618
protocol => $protocol,
4622
sub write_request_header {
4623
@_ == 4 || croak(q/Usage: $handle->write_request_header(method, request_uri, headers)/);
4624
my ($self, $method, $request_uri, $headers) = @_;
4626
return $self->write("$method $request_uri HTTP/1.1\x0D\x0A")
4627
+ $self->write_header_lines($headers);
4631
my ($self, $type, $timeout) = @_;
4632
$timeout = $self->{timeout}
4633
unless defined $timeout && $timeout >= 0;
4635
my $fd = fileno $self->{fh};
4636
defined $fd && $fd >= 0
4637
or croak(q/select(2): 'Bad file descriptor'/);
4640
my $pending = $timeout;
4643
vec(my $fdset = '', $fd, 1) = 1;
4646
$nfound = ($type eq 'read')
4647
? select($fdset, undef, undef, $pending)
4648
: select(undef, $fdset, undef, $pending) ;
4649
if ($nfound == -1) {
4651
or croak(qq/select(2): '$!'/);
4652
redo if !$timeout || ($pending = $timeout - (time - $initial)) > 0;
4662
@_ == 1 || @_ == 2 || croak(q/Usage: $handle->can_read([timeout])/);
4664
return $self->_do_timeout('read', @_)
4668
@_ == 1 || @_ == 2 || croak(q/Usage: $handle->can_write([timeout])/);
4670
return $self->_do_timeout('write', @_)
4672
} # HTTP::Micro::Handle
4671
4674
my $prog = <<'EOP';