This is a live mirror of the Perl 5 development currently hosted at https://github.com/perl/perl5
Upgrade cpan/CPAN-Meta to 2.120630
[perl5.git] / cpan / HTTP-Tiny / lib / HTTP / Tiny.pm
CommitLineData
a3ab329f 1# vim: ts=4 sts=4 sw=4 et:
a3ab329f 2package HTTP::Tiny;
a3ab329f
DG
3use strict;
4use warnings;
35265876 5# ABSTRACT: A small, simple, correct HTTP/1.1 client
6ce52845 6our $VERSION = '0.016'; # VERSION
a3ab329f
DG
7
8use Carp ();
9
10
11my @attributes;
12BEGIN {
13 @attributes = qw(agent default_headers max_redirect max_size proxy timeout);
14 no strict 'refs';
15 for my $accessor ( @attributes ) {
16 *{$accessor} = sub {
17 @_ > 1 ? $_[0]->{$accessor} = $_[1] : $_[0]->{$accessor};
18 };
19 }
20}
21
22sub new {
23 my($class, %args) = @_;
24 (my $agent = $class) =~ s{::}{-}g;
25 my $self = {
26 agent => $agent . "/" . ($class->VERSION || 0),
27 max_redirect => 5,
28 timeout => 60,
29 };
30 for my $key ( @attributes ) {
31 $self->{$key} = $args{$key} if exists $args{$key}
32 }
77ccfaeb
DG
33
34 # Never override proxy argument as this breaks backwards compat.
35 if (!exists $self->{proxy} && (my $http_proxy = $ENV{http_proxy})) {
36 if ($http_proxy =~ m{\Ahttp://[^/?#:@]+:\d+/?\z}) {
37 $self->{proxy} = $http_proxy;
38 }
39 else {
40 Carp::croak(qq{Environment 'http_proxy' must be in format http://<host>:<port>/\n});
41 }
42 }
43
a3ab329f
DG
44 return bless $self, $class;
45}
46
47
35265876
CBW
48for my $sub_name ( qw/get head put post delete/ ) {
49 my $req_method = uc $sub_name;
50 no strict 'refs';
51 eval <<"HERE";
52 sub $sub_name {
53 my (\$self, \$url, \$args) = \@_;
54 \@_ == 2 || (\@_ == 3 && ref \$args eq 'HASH')
55 or Carp::croak(q/Usage: \$http->$sub_name(URL, [HASHREF])/ . "\n");
56 return \$self->request('$req_method', \$url, \$args || {});
57 }
58HERE
59}
60
61
62sub post_form {
63 my ($self, $url, $data, $args) = @_;
64 (@_ == 3 || @_ == 4 && ref $args eq 'HASH')
65 or Carp::croak(q/Usage: $http->post_form(URL, DATAREF, [HASHREF])/ . "\n");
66
67 my $headers = {};
68 while ( my ($key, $value) = each %{$args->{headers} || {}} ) {
69 $headers->{lc $key} = $value;
70 }
71 delete $args->{headers};
72
73 return $self->request('POST', $url, {
74 %$args,
75 content => $self->www_form_urlencode($data),
76 headers => {
77 %$headers,
78 'content-type' => 'application/x-www-form-urlencoded'
79 },
80 }
81 );
a3ab329f
DG
82}
83
84
85sub mirror {
86 my ($self, $url, $file, $args) = @_;
87 @_ == 3 || (@_ == 4 && ref $args eq 'HASH')
77ccfaeb 88 or Carp::croak(q/Usage: $http->mirror(URL, FILE, [HASHREF])/ . "\n");
a3ab329f
DG
89 if ( -e $file and my $mtime = (stat($file))[9] ) {
90 $args->{headers}{'if-modified-since'} ||= $self->_http_date($mtime);
91 }
92 my $tempfile = $file . int(rand(2**31));
93 open my $fh, ">", $tempfile
77ccfaeb 94 or Carp::croak(qq/Error: Could not open temporary file $tempfile for downloading: $!\n/);
b06ddfb0 95 binmode $fh;
a3ab329f
DG
96 $args->{data_callback} = sub { print {$fh} $_[0] };
97 my $response = $self->request('GET', $url, $args);
98 close $fh
77ccfaeb 99 or Carp::croak(qq/Error: Could not close temporary file $tempfile: $!\n/);
a3ab329f
DG
100 if ( $response->{success} ) {
101 rename $tempfile, $file
77ccfaeb 102 or Carp::croak(qq/Error replacing $file with $tempfile: $!\n/);
a3ab329f
DG
103 my $lm = $response->{headers}{'last-modified'};
104 if ( $lm and my $mtime = $self->_parse_http_date($lm) ) {
105 utime $mtime, $mtime, $file;
106 }
107 }
108 $response->{success} ||= $response->{status} eq '304';
109 unlink $tempfile;
110 return $response;
111}
112
113
114my %idempotent = map { $_ => 1 } qw/GET HEAD PUT DELETE OPTIONS TRACE/;
115
116sub request {
117 my ($self, $method, $url, $args) = @_;
118 @_ == 3 || (@_ == 4 && ref $args eq 'HASH')
77ccfaeb 119 or Carp::croak(q/Usage: $http->request(METHOD, URL, [HASHREF])/ . "\n");
a3ab329f
DG
120 $args ||= {}; # we keep some state in this during _request
121
122 # RFC 2616 Section 8.1.4 mandates a single retry on broken socket
123 my $response;
124 for ( 0 .. 1 ) {
125 $response = eval { $self->_request($method, $url, $args) };
126 last unless $@ && $idempotent{$method}
127 && $@ =~ m{^(?:Socket closed|Unexpected end)};
128 }
129
130 if (my $e = "$@") {
131 $response = {
132 success => q{},
133 status => 599,
134 reason => 'Internal Exception',
135 content => $e,
136 headers => {
137 'content-type' => 'text/plain',
138 'content-length' => length $e,
139 }
140 };
141 }
142 return $response;
143}
144
35265876
CBW
145
146sub www_form_urlencode {
147 my ($self, $data) = @_;
148 (@_ == 2 && ref $data)
149 or Carp::croak(q/Usage: $http->www_form_urlencode(DATAREF)/ . "\n");
150 (ref $data eq 'HASH' || ref $data eq 'ARRAY')
151 or Carp::croak("form data must be a hash or array reference");
152
153 my @params = ref $data eq 'HASH' ? %$data : @$data;
154 @params % 2 == 0
155 or Carp::croak("form data reference must have an even number of terms\n");
156
157 my @terms;
158 while( @params ) {
159 my ($key, $value) = splice(@params, 0, 2);
160 if ( ref $value eq 'ARRAY' ) {
161 unshift @params, map { $key => $_ } @$value;
162 }
163 else {
164 push @terms, join("=", map { $self->_uri_escape($_) } $key, $value);
165 }
166 }
167
168 return join("&", sort @terms);
169}
170
171#--------------------------------------------------------------------------#
172# private methods
173#--------------------------------------------------------------------------#
174
a3ab329f
DG
175my %DefaultPort = (
176 http => 80,
177 https => 443,
178);
179
180sub _request {
181 my ($self, $method, $url, $args) = @_;
182
183 my ($scheme, $host, $port, $path_query) = $self->_split_url($url);
184
185 my $request = {
186 method => $method,
187 scheme => $scheme,
188 host_port => ($port == $DefaultPort{$scheme} ? $host : "$host:$port"),
189 uri => $path_query,
190 headers => {},
191 };
192
193 my $handle = HTTP::Tiny::Handle->new(timeout => $self->{timeout});
194
195 if ($self->{proxy}) {
196 $request->{uri} = "$scheme://$request->{host_port}$path_query";
77ccfaeb 197 die(qq/HTTPS via proxy is not supported\n/)
a3ab329f
DG
198 if $request->{scheme} eq 'https';
199 $handle->connect(($self->_split_url($self->{proxy}))[0..2]);
200 }
201 else {
202 $handle->connect($scheme, $host, $port);
203 }
204
205 $self->_prepare_headers_and_cb($request, $args);
206 $handle->write_request($request);
207
208 my $response;
209 do { $response = $handle->read_response_header }
210 until (substr($response->{status},0,1) ne '1');
211
212 if ( my @redir_args = $self->_maybe_redirect($request, $response, $args) ) {
213 $handle->close;
214 return $self->_request(@redir_args, $args);
215 }
216
217 if ($method eq 'HEAD' || $response->{status} =~ /^[23]04/) {
218 # response has no message body
219 }
220 else {
221 my $data_cb = $self->_prepare_data_cb($response, $args);
222 $handle->read_body($data_cb, $response);
223 }
224
225 $handle->close;
226 $response->{success} = substr($response->{status},0,1) eq '2';
227 return $response;
228}
229
230sub _prepare_headers_and_cb {
231 my ($self, $request, $args) = @_;
232
233 for ($self->{default_headers}, $args->{headers}) {
234 next unless defined;
235 while (my ($k, $v) = each %$_) {
236 $request->{headers}{lc $k} = $v;
237 }
238 }
239 $request->{headers}{'host'} = $request->{host_port};
240 $request->{headers}{'connection'} = "close";
241 $request->{headers}{'user-agent'} ||= $self->{agent};
242
243 if (defined $args->{content}) {
244 $request->{headers}{'content-type'} ||= "application/octet-stream";
245 if (ref $args->{content} eq 'CODE') {
246 $request->{headers}{'transfer-encoding'} = 'chunked'
247 unless $request->{headers}{'content-length'}
248 || $request->{headers}{'transfer-encoding'};
249 $request->{cb} = $args->{content};
250 }
251 else {
252 my $content = $args->{content};
253 if ( $] ge '5.008' ) {
254 utf8::downgrade($content, 1)
77ccfaeb 255 or die(qq/Wide character in request message body\n/);
a3ab329f
DG
256 }
257 $request->{headers}{'content-length'} = length $content
258 unless $request->{headers}{'content-length'}
259 || $request->{headers}{'transfer-encoding'};
260 $request->{cb} = sub { substr $content, 0, length $content, '' };
261 }
262 $request->{trailer_cb} = $args->{trailer_callback}
263 if ref $args->{trailer_callback} eq 'CODE';
264 }
265 return;
266}
267
268sub _prepare_data_cb {
269 my ($self, $response, $args) = @_;
270 my $data_cb = $args->{data_callback};
271 $response->{content} = '';
272
273 if (!$data_cb || $response->{status} !~ /^2/) {
274 if (defined $self->{max_size}) {
275 $data_cb = sub {
276 $_[1]->{content} .= $_[0];
277 die(qq/Size of response body exceeds the maximum allowed of $self->{max_size}\n/)
278 if length $_[1]->{content} > $self->{max_size};
279 };
280 }
281 else {
282 $data_cb = sub { $_[1]->{content} .= $_[0] };
283 }
284 }
285 return $data_cb;
286}
287
288sub _maybe_redirect {
289 my ($self, $request, $response, $args) = @_;
290 my $headers = $response->{headers};
291 my ($status, $method) = ($response->{status}, $request->{method});
292 if (($status eq '303' or ($status =~ /^30[127]/ && $method =~ /^GET|HEAD$/))
293 and $headers->{location}
294 and ++$args->{redirects} <= $self->{max_redirect}
295 ) {
296 my $location = ($headers->{location} =~ /^\//)
297 ? "$request->{scheme}://$request->{host_port}$headers->{location}"
298 : $headers->{location} ;
299 return (($status eq '303' ? 'GET' : $method), $location);
300 }
301 return;
302}
303
304sub _split_url {
305 my $url = pop;
306
307 # URI regex adapted from the URI module
308 my ($scheme, $authority, $path_query) = $url =~ m<\A([^:/?#]+)://([^/?#]*)([^#]*)>
77ccfaeb 309 or die(qq/Cannot parse URL: '$url'\n/);
a3ab329f
DG
310
311 $scheme = lc $scheme;
312 $path_query = "/$path_query" unless $path_query =~ m<\A/>;
313
314 my $host = (length($authority)) ? lc $authority : 'localhost';
315 $host =~ s/\A[^@]*@//; # userinfo
316 my $port = do {
317 $host =~ s/:([0-9]*)\z// && length $1
318 ? $1
319 : ($scheme eq 'http' ? 80 : $scheme eq 'https' ? 443 : undef);
320 };
321
322 return ($scheme, $host, $port, $path_query);
323}
324
325# Date conversions adapted from HTTP::Date
326my $DoW = "Sun|Mon|Tue|Wed|Thu|Fri|Sat";
327my $MoY = "Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec";
328sub _http_date {
329 my ($sec, $min, $hour, $mday, $mon, $year, $wday) = gmtime($_[1]);
330 return sprintf("%s, %02d %s %04d %02d:%02d:%02d GMT",
331 substr($DoW,$wday*4,3),
332 $mday, substr($MoY,$mon*4,3), $year+1900,
333 $hour, $min, $sec
334 );
335}
336
337sub _parse_http_date {
338 my ($self, $str) = @_;
339 require Time::Local;
340 my @tl_parts;
341 if ($str =~ /^[SMTWF][a-z]+, +(\d{1,2}) ($MoY) +(\d\d\d\d) +(\d\d):(\d\d):(\d\d) +GMT$/) {
342 @tl_parts = ($6, $5, $4, $1, (index($MoY,$2)/4), $3);
343 }
344 elsif ($str =~ /^[SMTWF][a-z]+, +(\d\d)-($MoY)-(\d{2,4}) +(\d\d):(\d\d):(\d\d) +GMT$/ ) {
345 @tl_parts = ($6, $5, $4, $1, (index($MoY,$2)/4), $3);
346 }
347 elsif ($str =~ /^[SMTWF][a-z]+ +($MoY) +(\d{1,2}) +(\d\d):(\d\d):(\d\d) +(?:[^0-9]+ +)?(\d\d\d\d)$/ ) {
348 @tl_parts = ($5, $4, $3, $2, (index($MoY,$1)/4), $6);
349 }
350 return eval {
351 my $t = @tl_parts ? Time::Local::timegm(@tl_parts) : -1;
352 $t < 0 ? undef : $t;
353 };
354}
355
35265876
CBW
356# URI escaping adapted from URI::Escape
357# c.f. http://www.w3.org/TR/html4/interact/forms.html#h-17.13.4.1
6ce52845 358# perl 5.6 ready UTF-8 encoding adapted from JSON::PP
35265876
CBW
359my %escapes = map { chr($_) => sprintf("%%%02X", $_) } 0..255;
360$escapes{' '}="+";
361my $unsafe_char = qr/[^A-Za-z0-9\-\._~]/;
362
363sub _uri_escape {
364 my ($self, $str) = @_;
6ce52845
CBW
365 if ( $] ge '5.008' ) {
366 utf8::encode($str);
367 }
368 else {
369 $str = pack("U*", unpack("C*", $str)) # UTF-8 encode a byte string
370 if ( length $str == do { use bytes; length $str } );
371 $str = pack("C*", unpack("C*", $str)); # clear UTF-8 flag
372 }
35265876
CBW
373 $str =~ s/($unsafe_char)/$escapes{$1}/ge;
374 return $str;
375}
376
a3ab329f
DG
377package
378 HTTP::Tiny::Handle; # hide from PAUSE/indexers
379use strict;
380use warnings;
381
a3ab329f
DG
382use Errno qw[EINTR EPIPE];
383use IO::Socket qw[SOCK_STREAM];
384
385sub BUFSIZE () { 32768 }
386
387my $Printable = sub {
388 local $_ = shift;
389 s/\r/\\r/g;
390 s/\n/\\n/g;
391 s/\t/\\t/g;
392 s/([^\x20-\x7E])/sprintf('\\x%.2X', ord($1))/ge;
393 $_;
394};
395
396my $Token = qr/[\x21\x23-\x27\x2A\x2B\x2D\x2E\x30-\x39\x41-\x5A\x5E-\x7A\x7C\x7E]/;
397
398sub new {
399 my ($class, %args) = @_;
400 return bless {
401 rbuf => '',
402 timeout => 60,
403 max_line_size => 16384,
404 max_header_lines => 64,
405 %args
406 }, $class;
407}
408
435aa301
DG
409my $ssl_verify_args = {
410 check_cn => "when_only",
411 wildcards_in_alt => "anywhere",
412 wildcards_in_cn => "anywhere"
413};
414
a3ab329f 415sub connect {
77ccfaeb 416 @_ == 4 || die(q/Usage: $handle->connect(scheme, host, port)/ . "\n");
a3ab329f
DG
417 my ($self, $scheme, $host, $port) = @_;
418
419 if ( $scheme eq 'https' ) {
420 eval "require IO::Socket::SSL"
421 unless exists $INC{'IO/Socket/SSL.pm'};
77ccfaeb 422 die(qq/IO::Socket::SSL must be installed for https support\n/)
a3ab329f
DG
423 unless $INC{'IO/Socket/SSL.pm'};
424 }
425 elsif ( $scheme ne 'http' ) {
77ccfaeb 426 die(qq/Unsupported URL scheme '$scheme'\n/);
a3ab329f
DG
427 }
428
429 $self->{fh} = 'IO::Socket::INET'->new(
430 PeerHost => $host,
431 PeerPort => $port,
432 Proto => 'tcp',
433 Type => SOCK_STREAM,
434 Timeout => $self->{timeout}
77ccfaeb 435 ) or die(qq/Could not connect to '$host:$port': $@\n/);
a3ab329f
DG
436
437 binmode($self->{fh})
77ccfaeb 438 or die(qq/Could not binmode() socket: '$!'\n/);
a3ab329f
DG
439
440 if ( $scheme eq 'https') {
441 IO::Socket::SSL->start_SSL($self->{fh});
442 ref($self->{fh}) eq 'IO::Socket::SSL'
435aa301
DG
443 or die(qq/SSL connection failed for $host\n/);
444 $self->{fh}->verify_hostname( $host, $ssl_verify_args )
445 or die(qq/SSL certificate not valid for $host\n/);
a3ab329f
DG
446 }
447
448 $self->{host} = $host;
449 $self->{port} = $port;
450
451 return $self;
452}
453
454sub close {
77ccfaeb 455 @_ == 1 || die(q/Usage: $handle->close()/ . "\n");
a3ab329f
DG
456 my ($self) = @_;
457 CORE::close($self->{fh})
77ccfaeb 458 or die(qq/Could not close socket: '$!'\n/);
a3ab329f
DG
459}
460
461sub write {
77ccfaeb 462 @_ == 2 || die(q/Usage: $handle->write(buf)/ . "\n");
a3ab329f
DG
463 my ($self, $buf) = @_;
464
465 if ( $] ge '5.008' ) {
466 utf8::downgrade($buf, 1)
77ccfaeb 467 or die(qq/Wide character in write()\n/);
a3ab329f
DG
468 }
469
470 my $len = length $buf;
471 my $off = 0;
472
473 local $SIG{PIPE} = 'IGNORE';
474
475 while () {
476 $self->can_write
77ccfaeb 477 or die(qq/Timed out while waiting for socket to become ready for writing\n/);
a3ab329f
DG
478 my $r = syswrite($self->{fh}, $buf, $len, $off);
479 if (defined $r) {
480 $len -= $r;
481 $off += $r;
482 last unless $len > 0;
483 }
484 elsif ($! == EPIPE) {
77ccfaeb 485 die(qq/Socket closed by remote server: $!\n/);
a3ab329f
DG
486 }
487 elsif ($! != EINTR) {
77ccfaeb 488 die(qq/Could not write to socket: '$!'\n/);
a3ab329f
DG
489 }
490 }
491 return $off;
492}
493
494sub read {
77ccfaeb 495 @_ == 2 || @_ == 3 || die(q/Usage: $handle->read(len [, allow_partial])/ . "\n");
a3ab329f
DG
496 my ($self, $len, $allow_partial) = @_;
497
498 my $buf = '';
499 my $got = length $self->{rbuf};
500
501 if ($got) {
502 my $take = ($got < $len) ? $got : $len;
503 $buf = substr($self->{rbuf}, 0, $take, '');
504 $len -= $take;
505 }
506
507 while ($len > 0) {
508 $self->can_read
77ccfaeb 509 or die(q/Timed out while waiting for socket to become ready for reading/ . "\n");
a3ab329f
DG
510 my $r = sysread($self->{fh}, $buf, $len, length $buf);
511 if (defined $r) {
512 last unless $r;
513 $len -= $r;
514 }
515 elsif ($! != EINTR) {
77ccfaeb 516 die(qq/Could not read from socket: '$!'\n/);
a3ab329f
DG
517 }
518 }
519 if ($len && !$allow_partial) {
77ccfaeb 520 die(qq/Unexpected end of stream\n/);
a3ab329f
DG
521 }
522 return $buf;
523}
524
525sub readline {
77ccfaeb 526 @_ == 1 || die(q/Usage: $handle->readline()/ . "\n");
a3ab329f
DG
527 my ($self) = @_;
528
529 while () {
530 if ($self->{rbuf} =~ s/\A ([^\x0D\x0A]* \x0D?\x0A)//x) {
531 return $1;
532 }
533 if (length $self->{rbuf} >= $self->{max_line_size}) {
77ccfaeb 534 die(qq/Line size exceeds the maximum allowed size of $self->{max_line_size}\n/);
a3ab329f
DG
535 }
536 $self->can_read
77ccfaeb 537 or die(qq/Timed out while waiting for socket to become ready for reading\n/);
a3ab329f
DG
538 my $r = sysread($self->{fh}, $self->{rbuf}, BUFSIZE, length $self->{rbuf});
539 if (defined $r) {
540 last unless $r;
541 }
542 elsif ($! != EINTR) {
77ccfaeb 543 die(qq/Could not read from socket: '$!'\n/);
a3ab329f
DG
544 }
545 }
77ccfaeb 546 die(qq/Unexpected end of stream while looking for line\n/);
a3ab329f
DG
547}
548
549sub read_header_lines {
77ccfaeb 550 @_ == 1 || @_ == 2 || die(q/Usage: $handle->read_header_lines([headers])/ . "\n");
a3ab329f
DG
551 my ($self, $headers) = @_;
552 $headers ||= {};
553 my $lines = 0;
554 my $val;
555
556 while () {
557 my $line = $self->readline;
558
559 if (++$lines >= $self->{max_header_lines}) {
77ccfaeb 560 die(qq/Header lines exceeds maximum number allowed of $self->{max_header_lines}\n/);
a3ab329f
DG
561 }
562 elsif ($line =~ /\A ([^\x00-\x1F\x7F:]+) : [\x09\x20]* ([^\x0D\x0A]*)/x) {
563 my ($field_name) = lc $1;
564 if (exists $headers->{$field_name}) {
565 for ($headers->{$field_name}) {
566 $_ = [$_] unless ref $_ eq "ARRAY";
567 push @$_, $2;
568 $val = \$_->[-1];
569 }
570 }
571 else {
572 $val = \($headers->{$field_name} = $2);
573 }
574 }
575 elsif ($line =~ /\A [\x09\x20]+ ([^\x0D\x0A]*)/x) {
576 $val
77ccfaeb 577 or die(qq/Unexpected header continuation line\n/);
a3ab329f
DG
578 next unless length $1;
579 $$val .= ' ' if length $$val;
580 $$val .= $1;
581 }
582 elsif ($line =~ /\A \x0D?\x0A \z/x) {
583 last;
584 }
585 else {
77ccfaeb 586 die(q/Malformed header line: / . $Printable->($line) . "\n");
a3ab329f
DG
587 }
588 }
589 return $headers;
590}
591
592sub write_request {
77ccfaeb 593 @_ == 2 || die(q/Usage: $handle->write_request(request)/ . "\n");
a3ab329f
DG
594 my($self, $request) = @_;
595 $self->write_request_header(@{$request}{qw/method uri headers/});
596 $self->write_body($request) if $request->{cb};
597 return;
598}
599
600my %HeaderCase = (
601 'content-md5' => 'Content-MD5',
602 'etag' => 'ETag',
603 'te' => 'TE',
604 'www-authenticate' => 'WWW-Authenticate',
605 'x-xss-protection' => 'X-XSS-Protection',
606);
607
608sub write_header_lines {
77ccfaeb 609 (@_ == 2 && ref $_[1] eq 'HASH') || die(q/Usage: $handle->write_header_lines(headers)/ . "\n");
a3ab329f
DG
610 my($self, $headers) = @_;
611
612 my $buf = '';
613 while (my ($k, $v) = each %$headers) {
614 my $field_name = lc $k;
615 if (exists $HeaderCase{$field_name}) {
616 $field_name = $HeaderCase{$field_name};
617 }
618 else {
619 $field_name =~ /\A $Token+ \z/xo
77ccfaeb 620 or die(q/Invalid HTTP header field name: / . $Printable->($field_name) . "\n");
a3ab329f
DG
621 $field_name =~ s/\b(\w)/\u$1/g;
622 $HeaderCase{lc $field_name} = $field_name;
623 }
624 for (ref $v eq 'ARRAY' ? @$v : $v) {
625 /[^\x0D\x0A]/
77ccfaeb 626 or die(qq/Invalid HTTP header field value ($field_name): / . $Printable->($_). "\n");
a3ab329f
DG
627 $buf .= "$field_name: $_\x0D\x0A";
628 }
629 }
630 $buf .= "\x0D\x0A";
631 return $self->write($buf);
632}
633
634sub read_body {
77ccfaeb 635 @_ == 3 || die(q/Usage: $handle->read_body(callback, response)/ . "\n");
a3ab329f
DG
636 my ($self, $cb, $response) = @_;
637 my $te = $response->{headers}{'transfer-encoding'} || '';
638 if ( grep { /chunked/i } ( ref $te eq 'ARRAY' ? @$te : $te ) ) {
639 $self->read_chunked_body($cb, $response);
640 }
641 else {
642 $self->read_content_body($cb, $response);
643 }
644 return;
645}
646
647sub write_body {
77ccfaeb 648 @_ == 2 || die(q/Usage: $handle->write_body(request)/ . "\n");
a3ab329f
DG
649 my ($self, $request) = @_;
650 if ($request->{headers}{'content-length'}) {
651 return $self->write_content_body($request);
652 }
653 else {
654 return $self->write_chunked_body($request);
655 }
656}
657
658sub read_content_body {
77ccfaeb 659 @_ == 3 || @_ == 4 || die(q/Usage: $handle->read_content_body(callback, response, [read_length])/ . "\n");
a3ab329f
DG
660 my ($self, $cb, $response, $content_length) = @_;
661 $content_length ||= $response->{headers}{'content-length'};
662
663 if ( $content_length ) {
664 my $len = $content_length;
665 while ($len > 0) {
666 my $read = ($len > BUFSIZE) ? BUFSIZE : $len;
667 $cb->($self->read($read, 0), $response);
668 $len -= $read;
669 }
670 }
671 else {
672 my $chunk;
673 $cb->($chunk, $response) while length( $chunk = $self->read(BUFSIZE, 1) );
674 }
675
676 return;
677}
678
679sub write_content_body {
77ccfaeb 680 @_ == 2 || die(q/Usage: $handle->write_content_body(request)/ . "\n");
a3ab329f
DG
681 my ($self, $request) = @_;
682
683 my ($len, $content_length) = (0, $request->{headers}{'content-length'});
684 while () {
685 my $data = $request->{cb}->();
686
687 defined $data && length $data
688 or last;
689
690 if ( $] ge '5.008' ) {
691 utf8::downgrade($data, 1)
77ccfaeb 692 or die(qq/Wide character in write_content()\n/);
a3ab329f
DG
693 }
694
695 $len += $self->write($data);
696 }
697
698 $len == $content_length
77ccfaeb 699 or die(qq/Content-Length missmatch (got: $len expected: $content_length)\n/);
a3ab329f
DG
700
701 return $len;
702}
703
704sub read_chunked_body {
77ccfaeb 705 @_ == 3 || die(q/Usage: $handle->read_chunked_body(callback, $response)/ . "\n");
a3ab329f
DG
706 my ($self, $cb, $response) = @_;
707
708 while () {
709 my $head = $self->readline;
710
711 $head =~ /\A ([A-Fa-f0-9]+)/x
77ccfaeb 712 or die(q/Malformed chunk head: / . $Printable->($head) . "\n");
a3ab329f
DG
713
714 my $len = hex($1)
715 or last;
716
717 $self->read_content_body($cb, $response, $len);
718
719 $self->read(2) eq "\x0D\x0A"
77ccfaeb 720 or die(qq/Malformed chunk: missing CRLF after chunk data\n/);
a3ab329f
DG
721 }
722 $self->read_header_lines($response->{headers});
723 return;
724}
725
726sub write_chunked_body {
77ccfaeb 727 @_ == 2 || die(q/Usage: $handle->write_chunked_body(request)/ . "\n");
a3ab329f
DG
728 my ($self, $request) = @_;
729
730 my $len = 0;
731 while () {
732 my $data = $request->{cb}->();
733
734 defined $data && length $data
735 or last;
736
737 if ( $] ge '5.008' ) {
738 utf8::downgrade($data, 1)
77ccfaeb 739 or die(qq/Wide character in write_chunked_body()\n/);
a3ab329f
DG
740 }
741
742 $len += length $data;
743
744 my $chunk = sprintf '%X', length $data;
745 $chunk .= "\x0D\x0A";
746 $chunk .= $data;
747 $chunk .= "\x0D\x0A";
748
749 $self->write($chunk);
750 }
751 $self->write("0\x0D\x0A");
752 $self->write_header_lines($request->{trailer_cb}->())
753 if ref $request->{trailer_cb} eq 'CODE';
754 return $len;
755}
756
757sub read_response_header {
77ccfaeb 758 @_ == 1 || die(q/Usage: $handle->read_response_header()/ . "\n");
a3ab329f
DG
759 my ($self) = @_;
760
761 my $line = $self->readline;
762
763 $line =~ /\A (HTTP\/(0*\d+\.0*\d+)) [\x09\x20]+ ([0-9]{3}) [\x09\x20]+ ([^\x0D\x0A]*) \x0D?\x0A/x
77ccfaeb 764 or die(q/Malformed Status-Line: / . $Printable->($line). "\n");
a3ab329f
DG
765
766 my ($protocol, $version, $status, $reason) = ($1, $2, $3, $4);
767
77ccfaeb 768 die (qq/Unsupported HTTP protocol: $protocol\n/)
a3ab329f
DG
769 unless $version =~ /0*1\.0*[01]/;
770
771 return {
772 status => $status,
773 reason => $reason,
774 headers => $self->read_header_lines,
775 protocol => $protocol,
776 };
777}
778
779sub write_request_header {
77ccfaeb 780 @_ == 4 || die(q/Usage: $handle->write_request_header(method, request_uri, headers)/ . "\n");
a3ab329f
DG
781 my ($self, $method, $request_uri, $headers) = @_;
782
783 return $self->write("$method $request_uri HTTP/1.1\x0D\x0A")
784 + $self->write_header_lines($headers);
785}
786
787sub _do_timeout {
788 my ($self, $type, $timeout) = @_;
789 $timeout = $self->{timeout}
790 unless defined $timeout && $timeout >= 0;
791
792 my $fd = fileno $self->{fh};
793 defined $fd && $fd >= 0
77ccfaeb 794 or die(qq/select(2): 'Bad file descriptor'\n/);
a3ab329f
DG
795
796 my $initial = time;
797 my $pending = $timeout;
798 my $nfound;
799
800 vec(my $fdset = '', $fd, 1) = 1;
801
802 while () {
803 $nfound = ($type eq 'read')
804 ? select($fdset, undef, undef, $pending)
805 : select(undef, $fdset, undef, $pending) ;
806 if ($nfound == -1) {
807 $! == EINTR
77ccfaeb 808 or die(qq/select(2): '$!'\n/);
a3ab329f
DG
809 redo if !$timeout || ($pending = $timeout - (time - $initial)) > 0;
810 $nfound = 0;
811 }
812 last;
813 }
814 $! = 0;
815 return $nfound;
816}
817
818sub can_read {
77ccfaeb 819 @_ == 1 || @_ == 2 || die(q/Usage: $handle->can_read([timeout])/ . "\n");
a3ab329f
DG
820 my $self = shift;
821 return $self->_do_timeout('read', @_)
822}
823
824sub can_write {
77ccfaeb 825 @_ == 1 || @_ == 2 || die(q/Usage: $handle->can_write([timeout])/ . "\n");
a3ab329f
DG
826 my $self = shift;
827 return $self->_do_timeout('write', @_)
828}
829
8301;
831
832
833
834__END__
835=pod
836
837=head1 NAME
838
839HTTP::Tiny - A small, simple, correct HTTP/1.1 client
840
841=head1 VERSION
842
6ce52845 843version 0.016
a3ab329f
DG
844
845=head1 SYNOPSIS
846
847 use HTTP::Tiny;
848
849 my $response = HTTP::Tiny->new->get('http://example.com/');
850
851 die "Failed!\n" unless $response->{success};
852
853 print "$response->{status} $response->{reason}\n";
854
855 while (my ($k, $v) = each %{$response->{headers}}) {
856 for (ref $v eq 'ARRAY' ? @$v : $v) {
857 print "$k: $_\n";
858 }
859 }
860
861 print $response->{content} if length $response->{content};
862
863=head1 DESCRIPTION
864
35265876 865This is a very simple HTTP/1.1 client, designed for doing simple GET
a3ab329f
DG
866requests without the overhead of a large framework like L<LWP::UserAgent>.
867
868It is more correct and more complete than L<HTTP::Lite>. It supports
869proxies (currently only non-authenticating ones) and redirection. It
870also correctly resumes after EINTR.
871
872=head1 METHODS
873
874=head2 new
875
876 $http = HTTP::Tiny->new( %attributes );
877
878This constructor returns a new HTTP::Tiny object. Valid attributes include:
879
880=over 4
881
882=item *
883
884agent
885
886A user-agent string (defaults to 'HTTP::Tiny/$VERSION')
887
888=item *
889
890default_headers
891
892A hashref of default headers to apply to requests
893
894=item *
895
896max_redirect
897
898Maximum number of redirects allowed (defaults to 5)
899
900=item *
901
902max_size
903
904Maximum response size (only when not using a data callback). If defined,
905responses larger than this will die with an error message
906
907=item *
908
909proxy
910
77ccfaeb 911URL of a proxy server to use (default is C<$ENV{http_proxy}> if set)
a3ab329f
DG
912
913=item *
914
915timeout
916
917Request timeout in seconds (default is 60)
918
919=back
920
35265876 921=head2 get|head|put|post|delete
a3ab329f
DG
922
923 $response = $http->get($url);
924 $response = $http->get($url, \%options);
35265876
CBW
925 $response = $http->head($url);
926
927These methods are shorthand for calling C<request()> for the given method. The
928URL must have unsafe characters escaped and international domain names encoded.
929See C<request()> for valid options and a description of the response.
930
931=head2 post_form
932
933 $response = $http->post_form($url, $form_data);
934 $response = $http->post_form($url, $form_data, \%options);
a3ab329f 935
35265876
CBW
936This method executes a C<POST> request and sends the key/value pairs from a
937form data hash or array reference to the given URL with a C<content-type> of
938C<application/x-www-form-urlencoded>. See documentation for the
939C<www_form_urlencode> method for details on the encoding.
940
941The URL must have unsafe characters escaped and international domain names
942encoded. See C<request()> for valid options and a description of the response.
943Any C<content-type> header or content in the options hashref will be ignored.
a3ab329f
DG
944
945=head2 mirror
946
947 $response = $http->mirror($url, $file, \%options)
948 if ( $response->{success} ) {
949 print "$file is up to date\n";
950 }
951
952Executes a C<GET> request for the URL and saves the response body to the file
953name provided. The URL must have unsafe characters escaped and international
954domain names encoded. If the file already exists, the request will includes an
955C<If-Modified-Since> header with the modification timestamp of the file. You
956may specificy a different C<If-Modified-Since> header yourself in the C<<
957$options->{headers} >> hash.
958
959The C<success> field of the response will be true if the status code is 2XX
960or 304 (unmodified).
961
962If the file was modified and the server response includes a properly
963formatted C<Last-Modified> header, the file modification time will
964be updated accordingly.
965
966=head2 request
967
968 $response = $http->request($method, $url);
969 $response = $http->request($method, $url, \%options);
970
435aa301
DG
971Executes an HTTP request of the given method type ('GET', 'HEAD', 'POST',
972'PUT', etc.) on the given URL. The URL must have unsafe characters escaped and
a3ab329f
DG
973international domain names encoded. A hashref of options may be appended to
974modify the request.
975
976Valid options are:
977
978=over 4
979
980=item *
981
982headers
983
984A hashref containing headers to include with the request. If the value for
985a header is an array reference, the header will be output multiple times with
986each value in the array. These headers over-write any default headers.
987
988=item *
989
990content
991
992A scalar to include as the body of the request OR a code reference
993that will be called iteratively to produce the body of the response
994
995=item *
996
997trailer_callback
998
999A code reference that will be called if it exists to provide a hashref
1000of trailing headers (only used with chunked transfer-encoding)
1001
1002=item *
1003
1004data_callback
1005
1006A code reference that will be called for each chunks of the response
1007body received.
1008
1009=back
1010
1011If the C<content> option is a code reference, it will be called iteratively
1012to provide the content body of the request. It should return the empty
1013string or undef when the iterator is exhausted.
1014
1015If the C<data_callback> option is provided, it will be called iteratively until
1016the entire response body is received. The first argument will be a string
1017containing a chunk of the response body, the second argument will be the
1018in-progress response hash reference, as described below. (This allows
1019customizing the action of the callback based on the C<status> or C<headers>
1020received prior to the content body.)
1021
1022The C<request> method returns a hashref containing the response. The hashref
1023will have the following keys:
1024
1025=over 4
1026
1027=item *
1028
1029success
1030
1031Boolean indicating whether the operation returned a 2XX status code
1032
1033=item *
1034
1035status
1036
1037The HTTP status code of the response
1038
1039=item *
1040
1041reason
1042
1043The response phrase returned by the server
1044
1045=item *
1046
1047content
1048
1049The body of the response. If the response does not have any content
1050or if a data callback is provided to consume the response body,
1051this will be the empty string
1052
1053=item *
1054
1055headers
1056
1057A hashref of header fields. All header field names will be normalized
1058to be lower case. If a header is repeated, the value will be an arrayref;
1059it will otherwise be a scalar string containing the value
1060
1061=back
1062
1063On an exception during the execution of the request, the C<status> field will
1064contain 599, and the C<content> field will contain the text of the exception.
1065
35265876
CBW
1066=head2 www_form_urlencode
1067
1068 $params = $http->www_form_urlencode( $data );
1069 $response = $http->get("http://example.com/query?$params");
1070
1071This method converts the key/value pairs from a data hash or array reference
1072into a C<x-www-form-urlencoded> string. The keys and values from the data
1073reference will be UTF-8 encoded and escaped per RFC 3986. If a value is an
1074array reference, the key will be repeated with each of the values of the array
1075reference. The key/value pairs in the resulting string will be sorted by key
1076and value.
1077
a3ab329f
DG
1078=for Pod::Coverage agent
1079default_headers
1080max_redirect
1081max_size
1082proxy
1083timeout
1084
1085=head1 LIMITATIONS
1086
1087HTTP::Tiny is I<conditionally compliant> with the
1088L<HTTP/1.1 specification|http://www.w3.org/Protocols/rfc2616/rfc2616.html>.
1089It attempts to meet all "MUST" requirements of the specification, but does not
1090implement all "SHOULD" requirements.
1091
1092Some particular limitations of note include:
1093
1094=over
1095
1096=item *
1097
1098HTTP::Tiny focuses on correct transport. Users are responsible for ensuring
1099that user-defined headers and content are compliant with the HTTP/1.1
1100specification.
1101
1102=item *
1103
1104Users must ensure that URLs are properly escaped for unsafe characters and that
1105international domain names are properly encoded to ASCII. See L<URI::Escape>,
1106L<URI::_punycode> and L<Net::IDN::Encode>.
1107
1108=item *
1109
1110Redirection is very strict against the specification. Redirection is only
1111automatic for response codes 301, 302 and 307 if the request method is 'GET' or
1112'HEAD'. Response code 303 is always converted into a 'GET' redirection, as
1113mandated by the specification. There is no automatic support for status 305
1114("Use proxy") redirections.
1115
1116=item *
1117
293dcbbb 1118Persistent connections are not supported. The C<Connection> header will
a3ab329f
DG
1119always be set to C<close>.
1120
1121=item *
1122
1123Direct C<https> connections are supported only if L<IO::Socket::SSL> is
1124installed. There is no support for C<https> connections via proxy.
b06ddfb0
DG
1125Any SSL certificate that matches the host is accepted -- SSL certificates
1126are not verified against certificate authorities.
a3ab329f
DG
1127
1128=item *
1129
1130Cookies are not directly supported. Users that set a C<Cookie> header
1131should also set C<max_redirect> to zero to ensure cookies are not
1132inappropriately re-transmitted.
1133
1134=item *
1135
77ccfaeb
DG
1136Only the C<http_proxy> environment variable is supported in the format
1137C<http://HOST:PORT/>. If a C<proxy> argument is passed to C<new> (including
1138undef), then the C<http_proxy> environment variable is ignored.
a3ab329f
DG
1139
1140=item *
1141
1142There is no provision for delaying a request body using an C<Expect> header.
1143Unexpected C<1XX> responses are silently ignored as per the specification.
1144
1145=item *
1146
1147Only 'chunked' C<Transfer-Encoding> is supported.
1148
1149=item *
1150
1151There is no support for a Request-URI of '*' for the 'OPTIONS' request.
1152
1153=back
1154
1155=head1 SEE ALSO
1156
1157=over 4
1158
1159=item *
1160
1161L<LWP::UserAgent>
1162
1163=back
1164
452d0b70 1165=for :stopwords cpan testmatrix url annocpan anno bugtracker rt cpants kwalitee diff irc mailto metadata placeholders
44de791a
DG
1166
1167=head1 SUPPORT
1168
1169=head2 Bugs / Feature Requests
1170
452d0b70
DG
1171Please report any bugs or feature requests by email to C<bug-http-tiny at rt.cpan.org>, or through
1172the web interface at L<http://rt.cpan.org/Public/Dist/Display.html?Name=HTTP-Tiny>. You will be automatically notified of any
1173progress on the request by the system.
44de791a
DG
1174
1175=head2 Source Code
1176
1177This is open source software. The code repository is available for
1178public review and contribution under the terms of the license.
1179
35265876 1180L<https://github.com/dagolden/p5-http-tiny>
44de791a 1181
35265876 1182 git clone https://github.com/dagolden/p5-http-tiny.git
44de791a 1183
a3ab329f
DG
1184=head1 AUTHORS
1185
1186=over 4
1187
1188=item *
1189
1190Christian Hansen <chansen@cpan.org>
1191
1192=item *
1193
1194David Golden <dagolden@cpan.org>
1195
1196=back
1197
1198=head1 COPYRIGHT AND LICENSE
1199
1200This software is copyright (c) 2011 by Christian Hansen.
1201
1202This is free software; you can redistribute it and/or modify it under
1203the same terms as the Perl 5 programming language system itself.
1204
1205=cut
1206