diff options
60 files changed, 3123 insertions, 0 deletions
@@ -1029,6 +1029,62 @@ cpan/Getopt-Long/t/gol-linkage.t See if Getopt::Long works cpan/Getopt-Long/t/gol-oo.t See if Getopt::Long works cpan/Getopt-Long/t/gol-xargv.t See if Getopt::Long works cpan/Getopt-Long/t/gol-xstring.t See if Getopt::Long works +cpan/HTTP-Tiny/lib/HTTP/Tiny.pm +cpan/HTTP-Tiny/t/000_load.t +cpan/HTTP-Tiny/t/001_api.t +cpan/HTTP-Tiny/t/002_croakage.t +cpan/HTTP-Tiny/t/00-compile.t +cpan/HTTP-Tiny/t/010_url.t +cpan/HTTP-Tiny/t/020_headers.t +cpan/HTTP-Tiny/t/030_response.t +cpan/HTTP-Tiny/t/040_content.t +cpan/HTTP-Tiny/t/050_chunked_body.t +cpan/HTTP-Tiny/t/060_http_date.t +cpan/HTTP-Tiny/t/100_get.t +cpan/HTTP-Tiny/t/110_mirror.t +cpan/HTTP-Tiny/t/120_put.t +cpan/HTTP-Tiny/t/130_redirect.t +cpan/HTTP-Tiny/t/cases/get-01.txt +cpan/HTTP-Tiny/t/cases/get-02.txt +cpan/HTTP-Tiny/t/cases/get-03.txt +cpan/HTTP-Tiny/t/cases/get-04.txt +cpan/HTTP-Tiny/t/cases/get-05.txt +cpan/HTTP-Tiny/t/cases/get-06.txt +cpan/HTTP-Tiny/t/cases/get-07.txt +cpan/HTTP-Tiny/t/cases/get-08.txt +cpan/HTTP-Tiny/t/cases/get-09.txt +cpan/HTTP-Tiny/t/cases/get-10.txt +cpan/HTTP-Tiny/t/cases/get-11.txt +cpan/HTTP-Tiny/t/cases/get-12.txt +cpan/HTTP-Tiny/t/cases/get-13.txt +cpan/HTTP-Tiny/t/cases/get-14.txt +cpan/HTTP-Tiny/t/cases/get-15.txt +cpan/HTTP-Tiny/t/cases/get-16.txt +cpan/HTTP-Tiny/t/cases/get-17.txt +cpan/HTTP-Tiny/t/cases/get-18.txt +cpan/HTTP-Tiny/t/cases/get-19.txt +cpan/HTTP-Tiny/t/cases/get-20.txt +cpan/HTTP-Tiny/t/cases/get-21.txt +cpan/HTTP-Tiny/t/cases/mirror-01.txt +cpan/HTTP-Tiny/t/cases/mirror-02.txt +cpan/HTTP-Tiny/t/cases/mirror-03.txt +cpan/HTTP-Tiny/t/cases/mirror-04.txt +cpan/HTTP-Tiny/t/cases/mirror-05.txt +cpan/HTTP-Tiny/t/cases/put-01.txt +cpan/HTTP-Tiny/t/cases/put-02.txt +cpan/HTTP-Tiny/t/cases/put-03.txt +cpan/HTTP-Tiny/t/cases/put-04.txt +cpan/HTTP-Tiny/t/cases/put-05.txt +cpan/HTTP-Tiny/t/cases/redirect-01.txt +cpan/HTTP-Tiny/t/cases/redirect-02.txt +cpan/HTTP-Tiny/t/cases/redirect-03.txt +cpan/HTTP-Tiny/t/cases/redirect-04.txt +cpan/HTTP-Tiny/t/cases/redirect-05.txt +cpan/HTTP-Tiny/t/cases/redirect-06.txt +cpan/HTTP-Tiny/t/cases/redirect-07.txt +cpan/HTTP-Tiny/t/cases/redirect-08.txt +cpan/HTTP-Tiny/t/cases/redirect-09.txt +cpan/HTTP-Tiny/t/Util.pm cpan/IO-Compress/Changes IO::Compress cpan/IO-Compress/examples/compress-zlib/filtdef IO::Compress cpan/IO-Compress/examples/compress-zlib/filtinf IO::Compress diff --git a/Porting/Maintainers.pl b/Porting/Maintainers.pl index 6057edabe6..a212efcc69 100755 --- a/Porting/Maintainers.pl +++ b/Porting/Maintainers.pl @@ -765,6 +765,19 @@ use File::Glob qw(:case); 'UPSTREAM' => 'cpan', }, + 'HTTP::Tiny' => + { + 'MAINTAINER' => 'dagolden', + 'DISTRIBUTION' => 'DAGOLDEN/HTTP-Tiny-0.008.tar.gz', + 'FILES' => q[cpan/HTTP-Tiny], + 'EXCLUDED' => [ + 't/200_live.t', + qr/^eg/, + qr/^xt/ + ], + 'UPSTREAM' => 'cpan', + }, + 'I18N::Collate' => { 'MAINTAINER' => 'p5p', diff --git a/cpan/HTTP-Tiny/lib/HTTP/Tiny.pm b/cpan/HTTP-Tiny/lib/HTTP/Tiny.pm new file mode 100644 index 0000000000..5a22982ea8 --- /dev/null +++ b/cpan/HTTP-Tiny/lib/HTTP/Tiny.pm @@ -0,0 +1,1069 @@ +# vim: ts=4 sts=4 sw=4 et: +# +# This file is part of HTTP-Tiny +# +# This software is copyright (c) 2011 by Christian Hansen. +# +# This is free software; you can redistribute it and/or modify it under +# the same terms as the Perl 5 programming language system itself. +# +package HTTP::Tiny; +BEGIN { + $HTTP::Tiny::VERSION = '0.008'; +} +use strict; +use warnings; +# ABSTRACT: A small, simple, correct HTTP/1.1 client + +use Carp (); + + +my @attributes; +BEGIN { + @attributes = qw(agent default_headers max_redirect max_size proxy timeout); + no strict 'refs'; + for my $accessor ( @attributes ) { + *{$accessor} = sub { + @_ > 1 ? $_[0]->{$accessor} = $_[1] : $_[0]->{$accessor}; + }; + } +} + +sub new { + my($class, %args) = @_; + (my $agent = $class) =~ s{::}{-}g; + my $self = { + agent => $agent . "/" . ($class->VERSION || 0), + max_redirect => 5, + timeout => 60, + }; + for my $key ( @attributes ) { + $self->{$key} = $args{$key} if exists $args{$key} + } + return bless $self, $class; +} + + +sub get { + my ($self, $url, $args) = @_; + @_ == 2 || (@_ == 3 && ref $args eq 'HASH') + or Carp::croak(q/Usage: $http->get(URL, [HASHREF])/); + return $self->request('GET', $url, $args || {}); +} + + +sub mirror { + my ($self, $url, $file, $args) = @_; + @_ == 3 || (@_ == 4 && ref $args eq 'HASH') + or Carp::croak(q/Usage: $http->mirror(URL, FILE, [HASHREF])/); + if ( -e $file and my $mtime = (stat($file))[9] ) { + $args->{headers}{'if-modified-since'} ||= $self->_http_date($mtime); + } + my $tempfile = $file . int(rand(2**31)); + open my $fh, ">", $tempfile + or Carp::croak(qq/Error: Could not open temporary file $tempfile for downloading: $!/); + $args->{data_callback} = sub { print {$fh} $_[0] }; + my $response = $self->request('GET', $url, $args); + close $fh + or Carp::croak(qq/Error: Could not close temporary file $tempfile: $!/); + if ( $response->{success} ) { + rename $tempfile, $file + or Carp::croak "Error replacing $file with $tempfile: $!\n"; + my $lm = $response->{headers}{'last-modified'}; + if ( $lm and my $mtime = $self->_parse_http_date($lm) ) { + utime $mtime, $mtime, $file; + } + } + $response->{success} ||= $response->{status} eq '304'; + unlink $tempfile; + return $response; +} + + +my %idempotent = map { $_ => 1 } qw/GET HEAD PUT DELETE OPTIONS TRACE/; + +sub request { + my ($self, $method, $url, $args) = @_; + @_ == 3 || (@_ == 4 && ref $args eq 'HASH') + or Carp::croak(q/Usage: $http->request(METHOD, URL, [HASHREF])/); + $args ||= {}; # we keep some state in this during _request + + # RFC 2616 Section 8.1.4 mandates a single retry on broken socket + my $response; + for ( 0 .. 1 ) { + $response = eval { $self->_request($method, $url, $args) }; + last unless $@ && $idempotent{$method} + && $@ =~ m{^(?:Socket closed|Unexpected end)}; + } + + if (my $e = "$@") { + $response = { + success => q{}, + status => 599, + reason => 'Internal Exception', + content => $e, + headers => { + 'content-type' => 'text/plain', + 'content-length' => length $e, + } + }; + } + return $response; +} + +my %DefaultPort = ( + http => 80, + https => 443, +); + +sub _request { + my ($self, $method, $url, $args) = @_; + + my ($scheme, $host, $port, $path_query) = $self->_split_url($url); + + my $request = { + method => $method, + scheme => $scheme, + host_port => ($port == $DefaultPort{$scheme} ? $host : "$host:$port"), + uri => $path_query, + headers => {}, + }; + + my $handle = HTTP::Tiny::Handle->new(timeout => $self->{timeout}); + + if ($self->{proxy}) { + $request->{uri} = "$scheme://$request->{host_port}$path_query"; + croak(qq/HTTPS via proxy is not supported/) + if $request->{scheme} eq 'https'; + $handle->connect(($self->_split_url($self->{proxy}))[0..2]); + } + else { + $handle->connect($scheme, $host, $port); + } + + $self->_prepare_headers_and_cb($request, $args); + $handle->write_request($request); + + my $response; + do { $response = $handle->read_response_header } + until (substr($response->{status},0,1) ne '1'); + + if ( my @redir_args = $self->_maybe_redirect($request, $response, $args) ) { + $handle->close; + return $self->_request(@redir_args, $args); + } + + if ($method eq 'HEAD' || $response->{status} =~ /^[23]04/) { + # response has no message body + } + else { + my $data_cb = $self->_prepare_data_cb($response, $args); + $handle->read_body($data_cb, $response); + } + + $handle->close; + $response->{success} = substr($response->{status},0,1) eq '2'; + return $response; +} + +sub _prepare_headers_and_cb { + my ($self, $request, $args) = @_; + + for ($self->{default_headers}, $args->{headers}) { + next unless defined; + while (my ($k, $v) = each %$_) { + $request->{headers}{lc $k} = $v; + } + } + $request->{headers}{'host'} = $request->{host_port}; + $request->{headers}{'connection'} = "close"; + $request->{headers}{'user-agent'} ||= $self->{agent}; + + if (defined $args->{content}) { + $request->{headers}{'content-type'} ||= "application/octet-stream"; + if (ref $args->{content} eq 'CODE') { + $request->{headers}{'transfer-encoding'} = 'chunked' + unless $request->{headers}{'content-length'} + || $request->{headers}{'transfer-encoding'}; + $request->{cb} = $args->{content}; + } + else { + my $content = $args->{content}; + if ( $] ge '5.008' ) { + utf8::downgrade($content, 1) + or Carp::croak(q/Wide character in request message body/); + } + $request->{headers}{'content-length'} = length $content + unless $request->{headers}{'content-length'} + || $request->{headers}{'transfer-encoding'}; + $request->{cb} = sub { substr $content, 0, length $content, '' }; + } + $request->{trailer_cb} = $args->{trailer_callback} + if ref $args->{trailer_callback} eq 'CODE'; + } + return; +} + +sub _prepare_data_cb { + my ($self, $response, $args) = @_; + my $data_cb = $args->{data_callback}; + $response->{content} = ''; + + if (!$data_cb || $response->{status} !~ /^2/) { + if (defined $self->{max_size}) { + $data_cb = sub { + $_[1]->{content} .= $_[0]; + die(qq/Size of response body exceeds the maximum allowed of $self->{max_size}\n/) + if length $_[1]->{content} > $self->{max_size}; + }; + } + else { + $data_cb = sub { $_[1]->{content} .= $_[0] }; + } + } + return $data_cb; +} + +sub _maybe_redirect { + my ($self, $request, $response, $args) = @_; + my $headers = $response->{headers}; + my ($status, $method) = ($response->{status}, $request->{method}); + if (($status eq '303' or ($status =~ /^30[127]/ && $method =~ /^GET|HEAD$/)) + and $headers->{location} + and ++$args->{redirects} <= $self->{max_redirect} + ) { + my $location = ($headers->{location} =~ /^\//) + ? "$request->{scheme}://$request->{host_port}$headers->{location}" + : $headers->{location} ; + return (($status eq '303' ? 'GET' : $method), $location); + } + return; +} + +sub _split_url { + my $url = pop; + + # URI regex adapted from the URI module + my ($scheme, $authority, $path_query) = $url =~ m<\A([^:/?#]+)://([^/?#]*)([^#]*)> + or Carp::croak(qq/Cannot parse URL: '$url'/); + + $scheme = lc $scheme; + $path_query = "/$path_query" unless $path_query =~ m<\A/>; + + my $host = (length($authority)) ? lc $authority : 'localhost'; + $host =~ s/\A[^@]*@//; # userinfo + my $port = do { + $host =~ s/:([0-9]*)\z// && length $1 + ? $1 + : ($scheme eq 'http' ? 80 : $scheme eq 'https' ? 443 : undef); + }; + + return ($scheme, $host, $port, $path_query); +} + +# Date conversions adapted from HTTP::Date +my $DoW = "Sun|Mon|Tue|Wed|Thu|Fri|Sat"; +my $MoY = "Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec"; +sub _http_date { + my ($sec, $min, $hour, $mday, $mon, $year, $wday) = gmtime($_[1]); + return sprintf("%s, %02d %s %04d %02d:%02d:%02d GMT", + substr($DoW,$wday*4,3), + $mday, substr($MoY,$mon*4,3), $year+1900, + $hour, $min, $sec + ); +} + +sub _parse_http_date { + my ($self, $str) = @_; + require Time::Local; + my @tl_parts; + if ($str =~ /^[SMTWF][a-z]+, +(\d{1,2}) ($MoY) +(\d\d\d\d) +(\d\d):(\d\d):(\d\d) +GMT$/) { + @tl_parts = ($6, $5, $4, $1, (index($MoY,$2)/4), $3); + } + elsif ($str =~ /^[SMTWF][a-z]+, +(\d\d)-($MoY)-(\d{2,4}) +(\d\d):(\d\d):(\d\d) +GMT$/ ) { + @tl_parts = ($6, $5, $4, $1, (index($MoY,$2)/4), $3); + } + elsif ($str =~ /^[SMTWF][a-z]+ +($MoY) +(\d{1,2}) +(\d\d):(\d\d):(\d\d) +(?:[^0-9]+ +)?(\d\d\d\d)$/ ) { + @tl_parts = ($5, $4, $3, $2, (index($MoY,$1)/4), $6); + } + return eval { + my $t = @tl_parts ? Time::Local::timegm(@tl_parts) : -1; + $t < 0 ? undef : $t; + }; +} + +package + HTTP::Tiny::Handle; # hide from PAUSE/indexers +use strict; +use warnings; + +use Carp qw[croak]; +use Errno qw[EINTR EPIPE]; +use IO::Socket qw[SOCK_STREAM]; + +sub BUFSIZE () { 32768 } + +my $Printable = sub { + local $_ = shift; + s/\r/\\r/g; + s/\n/\\n/g; + s/\t/\\t/g; + s/([^\x20-\x7E])/sprintf('\\x%.2X', ord($1))/ge; + $_; +}; + +my $Token = qr/[\x21\x23-\x27\x2A\x2B\x2D\x2E\x30-\x39\x41-\x5A\x5E-\x7A\x7C\x7E]/; + +sub new { + my ($class, %args) = @_; + return bless { + rbuf => '', + timeout => 60, + max_line_size => 16384, + max_header_lines => 64, + %args + }, $class; +} + +sub connect { + @_ == 4 || croak(q/Usage: $handle->connect(scheme, host, port)/); + my ($self, $scheme, $host, $port) = @_; + + if ( $scheme eq 'https' ) { + eval "require IO::Socket::SSL" + unless exists $INC{'IO/Socket/SSL.pm'}; + croak(qq/IO::Socket::SSL must be installed for https support\n/) + unless $INC{'IO/Socket/SSL.pm'}; + } + elsif ( $scheme ne 'http' ) { + croak(qq/Unsupported URL scheme '$scheme'/); + } + + $self->{fh} = 'IO::Socket::INET'->new( + PeerHost => $host, + PeerPort => $port, + Proto => 'tcp', + Type => SOCK_STREAM, + Timeout => $self->{timeout} + ) or croak(qq/Could not connect to '$host:$port': $@/); + + binmode($self->{fh}) + or croak(qq/Could not binmode() socket: '$!'/); + + if ( $scheme eq 'https') { + IO::Socket::SSL->start_SSL($self->{fh}); + ref($self->{fh}) eq 'IO::Socket::SSL' + and $self->{fh}->verify_hostname( $host, 'http' ) + or croak(qq/SSL connection failed for $host\n/); + } + + $self->{host} = $host; + $self->{port} = $port; + + return $self; +} + +sub close { + @_ == 1 || croak(q/Usage: $handle->close()/); + my ($self) = @_; + CORE::close($self->{fh}) + or croak(qq/Could not close socket: '$!'/); +} + +sub write { + @_ == 2 || croak(q/Usage: $handle->write(buf)/); + my ($self, $buf) = @_; + + if ( $] ge '5.008' ) { + utf8::downgrade($buf, 1) + or croak(q/Wide character in write()/); + } + + my $len = length $buf; + my $off = 0; + + local $SIG{PIPE} = 'IGNORE'; + + while () { + $self->can_write + or croak(q/Timed out while waiting for socket to become ready for writing/); + my $r = syswrite($self->{fh}, $buf, $len, $off); + if (defined $r) { + $len -= $r; + $off += $r; + last unless $len > 0; + } + elsif ($! == EPIPE) { + croak(qq/Socket closed by remote server: $!/); + } + elsif ($! != EINTR) { + croak(qq/Could not write to socket: '$!'/); + } + } + return $off; +} + +sub read { + @_ == 2 || @_ == 3 || croak(q/Usage: $handle->read(len [, allow_partial])/); + my ($self, $len, $allow_partial) = @_; + + my $buf = ''; + my $got = length $self->{rbuf}; + + if ($got) { + my $take = ($got < $len) ? $got : $len; + $buf = substr($self->{rbuf}, 0, $take, ''); + $len -= $take; + } + + while ($len > 0) { + $self->can_read + or croak(q/Timed out while waiting for socket to become ready for reading/); + my $r = sysread($self->{fh}, $buf, $len, length $buf); + if (defined $r) { + last unless $r; + $len -= $r; + } + elsif ($! != EINTR) { + croak(qq/Could not read from socket: '$!'/); + } + } + if ($len && !$allow_partial) { + croak(q/Unexpected end of stream/); + } + return $buf; +} + +sub readline { + @_ == 1 || croak(q/Usage: $handle->readline()/); + my ($self) = @_; + + while () { + if ($self->{rbuf} =~ s/\A ([^\x0D\x0A]* \x0D?\x0A)//x) { + return $1; + } + if (length $self->{rbuf} >= $self->{max_line_size}) { + croak(qq/Line size exceeds the maximum allowed size of $self->{max_line_size}/); + } + $self->can_read + or croak(q/Timed out while waiting for socket to become ready for reading/); + my $r = sysread($self->{fh}, $self->{rbuf}, BUFSIZE, length $self->{rbuf}); + if (defined $r) { + last unless $r; + } + elsif ($! != EINTR) { + croak(qq/Could not read from socket: '$!'/); + } + } + croak(q/Unexpected end of stream while looking for line/); +} + +sub read_header_lines { + @_ == 1 || @_ == 2 || croak(q/Usage: $handle->read_header_lines([headers])/); + my ($self, $headers) = @_; + $headers ||= {}; + my $lines = 0; + my $val; + + while () { + my $line = $self->readline; + + if (++$lines >= $self->{max_header_lines}) { + croak(qq/Header lines exceeds maximum number allowed of $self->{max_header_lines}/); + } + elsif ($line =~ /\A ([^\x00-\x1F\x7F:]+) : [\x09\x20]* ([^\x0D\x0A]*)/x) { + my ($field_name) = lc $1; + if (exists $headers->{$field_name}) { + for ($headers->{$field_name}) { + $_ = [$_] unless ref $_ eq "ARRAY"; + push @$_, $2; + $val = \$_->[-1]; + } + } + else { + $val = \($headers->{$field_name} = $2); + } + } + elsif ($line =~ /\A [\x09\x20]+ ([^\x0D\x0A]*)/x) { + $val + or croak(q/Unexpected header continuation line/); + next unless length $1; + $$val .= ' ' if length $$val; + $$val .= $1; + } + elsif ($line =~ /\A \x0D?\x0A \z/x) { + last; + } + else { + croak(q/Malformed header line: / . $Printable->($line)); + } + } + return $headers; +} + +sub write_request { + @_ == 2 || croak(q/Usage: $handle->write_request(request)/); + my($self, $request) = @_; + $self->write_request_header(@{$request}{qw/method uri headers/}); + $self->write_body($request) if $request->{cb}; + return; +} + +my %HeaderCase = ( + 'content-md5' => 'Content-MD5', + 'etag' => 'ETag', + 'te' => 'TE', + 'www-authenticate' => 'WWW-Authenticate', + 'x-xss-protection' => 'X-XSS-Protection', +); + +sub write_header_lines { + (@_ == 2 && ref $_[1] eq 'HASH') || croak(q/Usage: $handle->write_header_lines(headers)/); + my($self, $headers) = @_; + + my $buf = ''; + while (my ($k, $v) = each %$headers) { + my $field_name = lc $k; + if (exists $HeaderCase{$field_name}) { + $field_name = $HeaderCase{$field_name}; + } + else { + $field_name =~ /\A $Token+ \z/xo + or croak(q/Invalid HTTP header field name: / . $Printable->($field_name)); + $field_name =~ s/\b(\w)/\u$1/g; + $HeaderCase{lc $field_name} = $field_name; + } + for (ref $v eq 'ARRAY' ? @$v : $v) { + /[^\x0D\x0A]/ + or croak(qq/Invalid HTTP header field value ($field_name): / . $Printable->($_)); + $buf .= "$field_name: $_\x0D\x0A"; + } + } + $buf .= "\x0D\x0A"; + return $self->write($buf); +} + +sub read_body { + @_ == 3 || croak(q/Usage: $handle->read_body(callback, response)/); + my ($self, $cb, $response) = @_; + my $te = $response->{headers}{'transfer-encoding'} || ''; + if ( grep { /chunked/i } ( ref $te eq 'ARRAY' ? @$te : $te ) ) { + $self->read_chunked_body($cb, $response); + } + else { + $self->read_content_body($cb, $response); + } + return; +} + +sub write_body { + @_ == 2 || croak(q/Usage: $handle->write_body(request)/); + my ($self, $request) = @_; + if ($request->{headers}{'content-length'}) { + return $self->write_content_body($request); + } + else { + return $self->write_chunked_body($request); + } +} + +sub read_content_body { + @_ == 3 || @_ == 4 || croak(q/Usage: $handle->read_content_body(callback, response, [read_length])/); + my ($self, $cb, $response, $content_length) = @_; + $content_length ||= $response->{headers}{'content-length'}; + + if ( $content_length ) { + my $len = $content_length; + while ($len > 0) { + my $read = ($len > BUFSIZE) ? BUFSIZE : $len; + $cb->($self->read($read, 0), $response); + $len -= $read; + } + } + else { + my $chunk; + $cb->($chunk, $response) while length( $chunk = $self->read(BUFSIZE, 1) ); + } + + return; +} + +sub write_content_body { + @_ == 2 || croak(q/Usage: $handle->write_content_body(request)/); + my ($self, $request) = @_; + + my ($len, $content_length) = (0, $request->{headers}{'content-length'}); + while () { + my $data = $request->{cb}->(); + + defined $data && length $data + or last; + + if ( $] ge '5.008' ) { + utf8::downgrade($data, 1) + or croak(q/Wide character in write_content()/); + } + + $len += $self->write($data); + } + + $len == $content_length + or croak(qq/Content-Length missmatch (got: $len expected: $content_length)/); + + return $len; +} + +sub read_chunked_body { + @_ == 3 || croak(q/Usage: $handle->read_chunked_body(callback, $response)/); + my ($self, $cb, $response) = @_; + + while () { + my $head = $self->readline; + + $head =~ /\A ([A-Fa-f0-9]+)/x + or croak(q/Malformed chunk head: / . $Printable->($head)); + + my $len = hex($1) + or last; + + $self->read_content_body($cb, $response, $len); + + $self->read(2) eq "\x0D\x0A" + or croak(q/Malformed chunk: missing CRLF after chunk data/); + } + $self->read_header_lines($response->{headers}); + return; +} + +sub write_chunked_body { + @_ == 2 || croak(q/Usage: $handle->write_chunked_body(request)/); + my ($self, $request) = @_; + + my $len = 0; + while () { + my $data = $request->{cb}->(); + + defined $data && length $data + or last; + + if ( $] ge '5.008' ) { + utf8::downgrade($data, 1) + or croak(q/Wide character in write_chunked_body()/); + } + + $len += length $data; + + my $chunk = sprintf '%X', length $data; + $chunk .= "\x0D\x0A"; + $chunk .= $data; + $chunk .= "\x0D\x0A"; + + $self->write($chunk); + } + $self->write("0\x0D\x0A"); + $self->write_header_lines($request->{trailer_cb}->()) + if ref $request->{trailer_cb} eq 'CODE'; + return $len; +} + +sub read_response_header { + @_ == 1 || croak(q/Usage: $handle->read_response_header()/); + my ($self) = @_; + + my $line = $self->readline; + + $line =~ /\A (HTTP\/(0*\d+\.0*\d+)) [\x09\x20]+ ([0-9]{3}) [\x09\x20]+ ([^\x0D\x0A]*) \x0D?\x0A/x + or croak(q/Malformed Status-Line: / . $Printable->($line)); + + my ($protocol, $version, $status, $reason) = ($1, $2, $3, $4); + + croak (qq/Unsupported HTTP protocol: $protocol/) + unless $version =~ /0*1\.0*[01]/; + + return { + status => $status, + reason => $reason, + headers => $self->read_header_lines, + protocol => $protocol, + }; +} + +sub write_request_header { + @_ == 4 || croak(q/Usage: $handle->write_request_header(method, request_uri, headers)/); + my ($self, $method, $request_uri, $headers) = @_; + + return $self->write("$method $request_uri HTTP/1.1\x0D\x0A") + + $self->write_header_lines($headers); +} + +sub _do_timeout { + my ($self, $type, $timeout) = @_; + $timeout = $self->{timeout} + unless defined $timeout && $timeout >= 0; + + my $fd = fileno $self->{fh}; + defined $fd && $fd >= 0 + or croak(q/select(2): 'Bad file descriptor'/); + + my $initial = time; + my $pending = $timeout; + my $nfound; + + vec(my $fdset = '', $fd, 1) = 1; + + while () { + $nfound = ($type eq 'read') + ? select($fdset, undef, undef, $pending) + : select(undef, $fdset, undef, $pending) ; + if ($nfound == -1) { + $! == EINTR + or croak(qq/select(2): '$!'/); + redo if !$timeout || ($pending = $timeout - (time - $initial)) > 0; + $nfound = 0; + } + last; + } + $! = 0; + return $nfound; +} + +sub can_read { + @_ == 1 || @_ == 2 || croak(q/Usage: $handle->can_read([timeout])/); + my $self = shift; + return $self->_do_timeout('read', @_) +} + +sub can_write { + @_ == 1 || @_ == 2 || croak(q/Usage: $handle->can_write([timeout])/); + my $self = shift; + return $self->_do_timeout('write', @_) +} + +1; + + + +__END__ +=pod + +=head1 NAME + +HTTP::Tiny - A small, simple, correct HTTP/1.1 client + +=head1 VERSION + +version 0.008 + +=head1 SYNOPSIS + + use HTTP::Tiny; + + my $response = HTTP::Tiny->new->get('http://example.com/'); + + die "Failed!\n" unless $response->{success}; + + print "$response->{status} $response->{reason}\n"; + + while (my ($k, $v) = each %{$response->{headers}}) { + for (ref $v eq 'ARRAY' ? @$v : $v) { + print "$k: $_\n"; + } + } + + print $response->{content} if length $response->{content}; + +=head1 DESCRIPTION + +This is a very simple HTTP/1.1 client, designed primarily for doing simple GET +requests without the overhead of a large framework like L<LWP::UserAgent>. + +It is more correct and more complete than L<HTTP::Lite>. It supports +proxies (currently only non-authenticating ones) and redirection. It +also correctly resumes after EINTR. + +=head1 METHODS + +=head2 new + + $http = HTTP::Tiny->new( %attributes ); + +This constructor returns a new HTTP::Tiny object. Valid attributes include: + +=over 4 + +=item * + +agent + +A user-agent string (defaults to 'HTTP::Tiny/$VERSION') + +=item * + +default_headers + +A hashref of default headers to apply to requests + +=item * + +max_redirect + +Maximum number of redirects allowed (defaults to 5) + +=item * + +max_size + +Maximum response size (only when not using a data callback). If defined, +responses larger than this will die with an error message + +=item * + +proxy + +URL of a proxy server to use. + +=item * + +timeout + +Request timeout in seconds (default is 60) + +=back + +=head2 get + + $response = $http->get($url); + $response = $http->get($url, \%options); + +Executes a C<GET> request for the given URL. The URL must have unsafe +characters escaped and international domain names encoded. Internally, it just +calls C<request()> with 'GET' as the method. See C<request()> for valid +options and a description of the response. + +=head2 mirror + + $response = $http->mirror($url, $file, \%options) + if ( $response->{success} ) { + print "$file is up to date\n"; + } + +Executes a C<GET> request for the URL and saves the response body to the file +name provided. The URL must have unsafe characters escaped and international +domain names encoded. If the file already exists, the request will includes an +C<If-Modified-Since> header with the modification timestamp of the file. You +may specificy a different C<If-Modified-Since> header yourself in the C<< +$options->{headers} >> hash. + +The C<success> field of the response will be true if the status code is 2XX +or 304 (unmodified). + +If the file was modified and the server response includes a properly +formatted C<Last-Modified> header, the file modification time will +be updated accordingly. + +=head2 request + + $response = $http->request($method, $url); + $response = $http->request($method, $url, \%options); + +Executes an HTTP request of the given method type ('GET', 'HEAD', 'PUT', etc.) +on the given URL. The URL must have unsafe characters escaped and +international domain names encoded. A hashref of options may be appended to +modify the request. + +Valid options are: + +=over 4 + +=item * + +headers + +A hashref containing headers to include with the request. If the value for +a header is an array reference, the header will be output multiple times with +each value in the array. These headers over-write any default headers. + +=item * + +content + +A scalar to include as the body of the request OR a code reference +that will be called iteratively to produce the body of the response + +=item * + +trailer_callback + +A code reference that will be called if it exists to provide a hashref +of trailing headers (only used with chunked transfer-encoding) + +=item * + +data_callback + +A code reference that will be called for each chunks of the response +body received. + +=back + +If the C<content> option is a code reference, it will be called iteratively +to provide the content body of the request. It should return the empty +string or undef when the iterator is exhausted. + +If the C<data_callback> option is provided, it will be called iteratively until +the entire response body is received. The first argument will be a string +containing a chunk of the response body, the second argument will be the +in-progress response hash reference, as described below. (This allows +customizing the action of the callback based on the C<status> or C<headers> +received prior to the content body.) + +The C<request> method returns a hashref containing the response. The hashref +will have the following keys: + +=over 4 + +=item * + +success + +Boolean indicating whether the operation returned a 2XX status code + +=item * + +status + +The HTTP status code of the response + +=item * + +reason + +The response phrase returned by the server + +=item * + +content + +The body of the response. If the response does not have any content +or if a data callback is provided to consume the response body, +this will be the empty string + +=item * + +headers + +A hashref of header fields. All header field names will be normalized +to be lower case. If a header is repeated, the value will be an arrayref; +it will otherwise be a scalar string containing the value + +=back + +On an exception during the execution of the request, the C<status> field will +contain 599, and the C<content> field will contain the text of the exception. + +=for Pod::Coverage agent +default_headers +max_redirect +max_size +proxy +timeout + +=head1 LIMITATIONS + +HTTP::Tiny is I<conditionally compliant> with the +L<HTTP/1.1 specification|http://www.w3.org/Protocols/rfc2616/rfc2616.html>. +It attempts to meet all "MUST" requirements of the specification, but does not +implement all "SHOULD" requirements. + +Some particular limitations of note include: + +=over + +=item * + +HTTP::Tiny focuses on correct transport. Users are responsible for ensuring +that user-defined headers and content are compliant with the HTTP/1.1 +specification. + +=item * + +Users must ensure that URLs are properly escaped for unsafe characters and that +international domain names are properly encoded to ASCII. See L<URI::Escape>, +L<URI::_punycode> and L<Net::IDN::Encode>. + +=item * + +Redirection is very strict against the specification. Redirection is only +automatic for response codes 301, 302 and 307 if the request method is 'GET' or +'HEAD'. Response code 303 is always converted into a 'GET' redirection, as +mandated by the specification. There is no automatic support for status 305 +("Use proxy") redirections. + +=item * + +Persistant connections are not supported. The C<Connection> header will +always be set to C<close>. + +=item * + +Direct C<https> connections are supported only if L<IO::Socket::SSL> is +installed. There is no support for C<https> connections via proxy. + +=item * + +Cookies are not directly supported. Users that set a C<Cookie> header +should also set C<max_redirect> to zero to ensure cookies are not +inappropriately re-transmitted. + +=item * + +Proxy environment variables are not supported. + +=item * + +There is no provision for delaying a request body using an C<Expect> header. +Unexpected C<1XX> responses are silently ignored as per the specification. + +=item * + +Only 'chunked' C<Transfer-Encoding> is supported. + +=item * + +There is no support for a Request-URI of '*' for the 'OPTIONS' request. + +=back + +=head1 SEE ALSO + +=over 4 + +=item * + +L<LWP::UserAgent> + +=back + +=head1 AUTHORS + +=over 4 + +=item * + +Christian Hansen <chansen@cpan.org> + +=item * + +David Golden <dagolden@cpan.org> + +=back + +=head1 COPYRIGHT AND LICENSE + +This software is copyright (c) 2011 by Christian Hansen. + +This is free software; you can redistribute it and/or modify it under +the same terms as the Perl 5 programming language system itself. + +=cut + diff --git a/cpan/HTTP-Tiny/t/00-compile.t b/cpan/HTTP-Tiny/t/00-compile.t new file mode 100644 index 0000000000..277185d8e5 --- /dev/null +++ b/cpan/HTTP-Tiny/t/00-compile.t @@ -0,0 +1,56 @@ +#!perl +# +# This file is part of HTTP-Tiny +# +# This software is copyright (c) 2011 by Christian Hansen. +# +# This is free software; you can redistribute it and/or modify it under +# the same terms as the Perl 5 programming language system itself. +# + +use strict; +use warnings; + +use Test::More; + + + +use File::Find; +use File::Temp qw{ tempdir }; + +my @modules; +find( + sub { + return if $File::Find::name !~ /\.pm\z/; + my $found = $File::Find::name; + $found =~ s{^lib/}{}; + $found =~ s{[/\\]}{::}g; + $found =~ s/\.pm$//; + # nothing to skip + push @modules, $found; + }, + 'lib', +); + +my @scripts = glob "bin/*"; + +my $plan = scalar(@modules) + scalar(@scripts); +$plan ? (plan tests => $plan) : (plan skip_all => "no tests to run"); + +{ + # fake home for cpan-testers + local $ENV{HOME} = tempdir( CLEANUP => 1 ); + + like( qx{ $^X -Ilib -e "require $_; print '$_ ok'" }, qr/^\s*$_ ok/s, "$_ loaded ok" ) + for sort @modules; + + SKIP: { + eval "use Test::Script 1.05; 1;"; + skip "Test::Script needed to test script compilation", scalar(@scripts) if $@; + foreach my $file ( @scripts ) { + my $script = $file; + $script =~ s!.*/!!; + script_compiles( $file, "$script script compiles" ); + } + } +} diff --git a/cpan/HTTP-Tiny/t/000_load.t b/cpan/HTTP-Tiny/t/000_load.t new file mode 100644 index 0000000000..b625b15d3a --- /dev/null +++ b/cpan/HTTP-Tiny/t/000_load.t @@ -0,0 +1,21 @@ +#!perl +# +# This file is part of HTTP-Tiny +# +# This software is copyright (c) 2011 by Christian Hansen. +# +# This is free software; you can redistribute it and/or modify it under +# the same terms as the Perl 5 programming language system itself. +# + +use strict; +use warnings; + +use Test::More tests => 1; + +BEGIN { + use_ok('HTTP::Tiny'); +} + +diag("HTTP::Tiny $HTTP::Tiny::VERSION, Perl $], $^X"); + diff --git a/cpan/HTTP-Tiny/t/001_api.t b/cpan/HTTP-Tiny/t/001_api.t new file mode 100644 index 0000000000..12050a07a0 --- /dev/null +++ b/cpan/HTTP-Tiny/t/001_api.t @@ -0,0 +1,34 @@ +#!perl +# +# This file is part of HTTP-Tiny +# +# This software is copyright (c) 2011 by Christian Hansen. +# +# This is free software; you can redistribute it and/or modify it under +# the same terms as the Perl 5 programming language system itself. +# + +use strict; +use warnings; + +use Test::More tests => 2; +use HTTP::Tiny; + +my @accessors = qw(agent default_headers max_redirect max_size proxy timeout); +my @methods = qw(new get request mirror); + +my %api; +@api{@accessors} = (1) x @accessors; +@api{@methods} = (1) x @accessors; + +can_ok('HTTP::Tiny', @methods, @accessors); + +my @extra = + grep {! $api{$_} } + grep { $_ !~ /\A_/ } + grep {; no strict 'refs'; *{"HTTP::Tiny::$_"}{CODE} } + sort keys %HTTP::Tiny::; + +ok( ! scalar @extra, "No unexpected subroutines defined" ) + or diag "Found: @extra"; + diff --git a/cpan/HTTP-Tiny/t/002_croakage.t b/cpan/HTTP-Tiny/t/002_croakage.t new file mode 100644 index 0000000000..317f0a7cb4 --- /dev/null +++ b/cpan/HTTP-Tiny/t/002_croakage.t @@ -0,0 +1,47 @@ +#!perl +# +# This file is part of HTTP-Tiny +# +# This software is copyright (c) 2011 by Christian Hansen. +# +# This is free software; you can redistribute it and/or modify it under +# the same terms as the Perl 5 programming language system itself. +# + +use strict; +use warnings; + +use Test::More; +use HTTP::Tiny; + +my %usage = ( + 'get' => q/Usage: $http->get(URL, [HASHREF])/, + 'mirror' => q/Usage: $http->mirror(URL, FILE, [HASHREF])/, + 'request' => q/Usage: $http->request(METHOD, URL, [HASHREF])/, +); + +my @cases = ( + ['get'], + ['get','http://www.example.com/','extra'], + ['get','http://www.example.com/','extra', 'extra'], + ['mirror'], + ['mirror','http://www.example.com/',], + ['mirror','http://www.example.com/','extra', 'extra'], + ['mirror','http://www.example.com/','extra', 'extra', 'extra'], + ['request'], + ['request','GET'], + ['request','GET','http://www.example.com/','extra'], + ['request','GET','http://www.example.com/','extra', 'extra'], +); + +my $http = HTTP::Tiny->new; + +for my $c ( @cases ) { + my ($method, @args) = @$c; + eval {$http->$method(@args)}; + my $err = $@; + like ($err, qr/\Q$usage{$method}\E/, join("|",@$c) ); +} + +done_testing; + diff --git a/cpan/HTTP-Tiny/t/010_url.t b/cpan/HTTP-Tiny/t/010_url.t new file mode 100644 index 0000000000..58ba1179ab --- /dev/null +++ b/cpan/HTTP-Tiny/t/010_url.t @@ -0,0 +1,42 @@ +#!perl +# +# This file is part of HTTP-Tiny +# +# This software is copyright (c) 2011 by Christian Hansen. +# +# This is free software; you can redistribute it and/or modify it under +# the same terms as the Perl 5 programming language system itself. +# + +use strict; +use warnings; + +use Test::More; +use HTTP::Tiny; + +my @tests = ( + [ 'HtTp://Example.COM/', 'http', 'example.com', 80, '/' ], + [ 'HtTp://Example.com:1024/', 'http', 'example.com', 1024, '/' ], + [ 'http://example.com', 'http', 'example.com', 80, '/' ], + [ 'http://example.com:', 'http', 'example.com', 80, '/' ], + [ 'http://foo@example.com:', 'http', 'example.com', 80, '/' ], + [ 'http://@example.com:', 'http', 'example.com', 80, '/' ], + [ 'http://example.com?foo=bar', 'http', 'example.com', 80, '/?foo=bar' ], + [ 'http://example.com?foo=bar#fragment', 'http', 'example.com', 80, '/?foo=bar' ], + [ 'http://example.com/path?foo=bar', 'http', 'example.com', 80, '/path?foo=bar' ], + [ 'http:///path?foo=bar', 'http', 'localhost', 80, '/path?foo=bar' ], + [ 'HTTPS://example.com/', 'https', 'example.com', 443, '/' ], + [ 'http://[::]:1024', 'http', '[::]', 1024, '/' ], + [ 'xxx://foo/', 'xxx', 'foo', undef, '/' ], +); + +plan tests => scalar @tests; + +for my $test (@tests) { + my $url = shift(@$test); + my $got = [ HTTP::Tiny->_split_url($url) ]; + my $exp = $test; + is_deeply($got, $exp, "->split_url('$url')"); +} + + diff --git a/cpan/HTTP-Tiny/t/020_headers.t b/cpan/HTTP-Tiny/t/020_headers.t new file mode 100644 index 0000000000..3a6fc63b8d --- /dev/null +++ b/cpan/HTTP-Tiny/t/020_headers.t @@ -0,0 +1,59 @@ +#!perl +# +# This file is part of HTTP-Tiny +# +# This software is copyright (c) 2011 by Christian Hansen. +# +# This is free software; you can redistribute it and/or modify it under +# the same terms as the Perl 5 programming language system itself. +# + +use strict; +use warnings; + +use Test::More qw[no_plan]; +use t::Util qw[tmpfile rewind $CRLF $LF]; +use HTTP::Tiny; + +{ + no warnings 'redefine'; + sub HTTP::Tiny::Handle::can_read { 1 }; + sub HTTP::Tiny::Handle::can_write { 1 }; +} + +{ + my $header = join $CRLF, 'Foo: Foo', 'Foo: Baz', 'Bar: Bar', '', ''; + my $fh = tmpfile($header); + my $exp = { foo => ['Foo', 'Baz'], bar => 'Bar' }; + my $handle = HTTP::Tiny::Handle->new(fh => $fh); + my $got = $handle->read_header_lines; + is_deeply($got, $exp, "->read_header_lines() CRLF"); +} + +{ + my $header = join $LF, 'Foo: Foo', 'Foo: Baz', 'Bar: Bar', '', ''; + my $fh = tmpfile($header); + my $exp = { foo => ['Foo', 'Baz'], bar => 'Bar' }; + my $handle = HTTP::Tiny::Handle->new(fh => $fh); + my $got = $handle->read_header_lines; + is_deeply($got, $exp, "->read_header_lines() LF"); +} + +{ + my $header = "Foo: $CRLF\x09Bar$CRLF\x09$CRLF\x09Baz$CRLF$CRLF"; + my $fh = tmpfile($header); + my $exp = { foo => 'Bar Baz' }; + my $handle = HTTP::Tiny::Handle->new(fh => $fh); + my $got = $handle->read_header_lines; + is_deeply($got, $exp, "->read_header_lines() insane continuations"); +} + +{ + my $fh = tmpfile(); + my $handle = HTTP::Tiny::Handle->new(fh => $fh); + my $headers = { foo => ['Foo', 'Baz'], bar => 'Bar' }; + $handle->write_header_lines($headers); + rewind($fh); + is_deeply($handle->read_header_lines, $headers, "roundtrip header lines"); +} + diff --git a/cpan/HTTP-Tiny/t/030_response.t b/cpan/HTTP-Tiny/t/030_response.t new file mode 100644 index 0000000000..fbeea58133 --- /dev/null +++ b/cpan/HTTP-Tiny/t/030_response.t @@ -0,0 +1,43 @@ +#!perl +# +# This file is part of HTTP-Tiny +# +# This software is copyright (c) 2011 by Christian Hansen. +# +# This is free software; you can redistribute it and/or modify it under +# the same terms as the Perl 5 programming language system itself. +# + +use strict; +use warnings; + +use Test::More qw[no_plan]; +use t::Util qw[tmpfile rewind $CRLF $LF]; +use HTTP::Tiny; + +sub _header { + return [ @{$_[0]}{qw/status reason headers protocol/} ] +} + +{ + no warnings 'redefine'; + sub HTTP::Tiny::Handle::can_read { 1 }; + sub HTTP::Tiny::Handle::can_write { 1 }; +} + +{ + my $response = join $CRLF, 'HTTP/1.1 200 OK', 'Foo: Foo', 'Bar: Bar', '', ''; + my $fh = tmpfile($response); + my $handle = HTTP::Tiny::Handle->new(fh => $fh); + my $exp = [ 200, 'OK', { foo => 'Foo', bar => 'Bar' }, 'HTTP/1.1' ]; + is_deeply(_header($handle->read_response_header), $exp, "->read_response_header CRLF"); +} + +{ + my $response = join $LF, 'HTTP/1.1 200 OK', 'Foo: Foo', 'Bar: Bar', '', ''; + my $fh = tmpfile($response); + my $handle = HTTP::Tiny::Handle->new(fh => $fh); + my $exp = [ 200, 'OK', { foo => 'Foo', bar => 'Bar' }, 'HTTP/1.1' ]; + is_deeply(_header($handle->read_response_header), $exp, "->read_response_header LF"); +} + diff --git a/cpan/HTTP-Tiny/t/040_content.t b/cpan/HTTP-Tiny/t/040_content.t new file mode 100644 index 0000000000..e69bbedc4b --- /dev/null +++ b/cpan/HTTP-Tiny/t/040_content.t @@ -0,0 +1,48 @@ +#!perl +# +# This file is part of HTTP-Tiny +# +# This software is copyright (c) 2011 by Christian Hansen. +# +# This is free software; you can redistribute it and/or modify it under +# the same terms as the Perl 5 programming language system itself. +# + +use strict; +use warnings; + +use Test::More qw[no_plan]; +use t::Util qw[tmpfile rewind $CRLF $LF]; +use HTTP::Tiny; + +{ + no warnings 'redefine'; + sub HTTP::Tiny::Handle::can_read { 1 }; + sub HTTP::Tiny::Handle::can_write { 1 }; +} + +{ + my $chunk = join('', '0' .. '9', 'A' .. 'Z', 'a' .. 'z', '_', $LF) x 16; # 1024 + my $fh = tmpfile(); + my $handle = HTTP::Tiny::Handle->new(fh => $fh); + my $nchunks = 128; + my $length = $nchunks * length $chunk; + + { + my $request = { + cb => sub { $nchunks-- ? $chunk : undef }, + headers => { 'content-length' => $length } + }; + my $got = $handle->write_content_body($request); + is($got, $length, "written $length octets"); + } + + rewind($fh); + + { + my $got = 0; + $handle->read_content_body(sub { $got += length $_[0] }, {}, $length); + is($got, $length, "read $length octets"); + } +} + diff --git a/cpan/HTTP-Tiny/t/050_chunked_body.t b/cpan/HTTP-Tiny/t/050_chunked_body.t new file mode 100644 index 0000000000..16c9cf7a7c --- /dev/null +++ b/cpan/HTTP-Tiny/t/050_chunked_body.t @@ -0,0 +1,66 @@ +#!perl +# +# This file is part of HTTP-Tiny +# +# This software is copyright (c) 2011 by Christian Hansen. +# +# This is free software; you can redistribute it and/or modify it under +# the same terms as the Perl 5 programming language system itself. +# + +use strict; +use warnings; + +use Test::More qw[no_plan]; +use t::Util qw[tmpfile rewind $CRLF]; +use HTTP::Tiny; + +{ + no warnings 'redefine'; + sub HTTP::Tiny::Handle::can_read { 1 }; + sub HTTP::Tiny::Handle::can_write { 1 }; +} + +{ + my $body = join($CRLF, map { sprintf('%x', length $_) . $CRLF . $_ } 'A'..'Z', '') . $CRLF; + my $fh = tmpfile($body); + my $handle = HTTP::Tiny::Handle->new(fh => $fh); + my $exp = ['A'..'Z']; + my $got = []; + my $cb = sub { push @$got, $_[0] }; + my $response = { headers => {} }; + $handle->read_chunked_body($cb, $response); + is_deeply($response->{headers}, {}, 'chunked trailers'); + is_deeply($got, $exp, "chunked chunks"); +} + +{ + my $fh = tmpfile(); + my $handle = HTTP::Tiny::Handle->new(fh => $fh); + + my $exp = ['A'..'Z']; + my $trailers = { foo => 'Bar', bar => 'Baz' }; + my $got = []; + + { + my @chunks = @$exp; + my $request = { + cb => sub { shift @chunks }, + trailer_cb => sub { $trailers }, + }; + $handle->write_chunked_body($request); + } + + rewind($fh); + + { + my $cb = sub { push @$got, $_[0] }; + my $response = { headers => {} }; + $handle->read_chunked_body($cb, $response); + is_deeply($response->{headers}, $trailers, 'roundtrip chunked trailers'); + } + + is_deeply($got, $exp, "roundtrip chunked chunks"); +} + + diff --git a/cpan/HTTP-Tiny/t/060_http_date.t b/cpan/HTTP-Tiny/t/060_http_date.t new file mode 100644 index 0000000000..bd7077a05d --- /dev/null +++ b/cpan/HTTP-Tiny/t/060_http_date.t @@ -0,0 +1,37 @@ +#!perl +# +# This file is part of HTTP-Tiny +# +# This software is copyright (c) 2011 by Christian Hansen. +# +# This is free software; you can redistribute it and/or modify it under +# the same terms as the Perl 5 programming language system itself. +# + +use strict; +use warnings; + +use Test::More; +use HTTP::Tiny; + +# test cases adapted from HTTP::Date +my $epoch = 760233600; + +my @cases = ( + ['Thu, 03 Feb 1994 00:00:00 GMT', 'RFC822+RFC1123'], + ['Thu, 3 Feb 1994 00:00:00 GMT', 'broken RFC822+RFC1123'], + ['Thursday, 03-Feb-94 00:00:00 GMT', 'old rfc850 HTTP format'], + ['Thursday, 03-Feb-1994 00:00:00 GMT', 'broken rfc850 HTTP format'], + ['Thu Feb 3 00:00:00 GMT 1994', 'ctime format'], + ['Thu Feb 3 00:00:00 1994', 'same as ctime, except no TZ'], +); + +plan tests => 1 + @cases; + +is(HTTP::Tiny->_http_date($epoch), $cases[0][0], "epoch -> RFC822/RFC1123"); + +for my $c ( @cases ) { + is( HTTP::Tiny->_parse_http_date($c->[0]), $epoch, $c->[1] . " -> epoch"); +} + + diff --git a/cpan/HTTP-Tiny/t/100_get.t b/cpan/HTTP-Tiny/t/100_get.t new file mode 100644 index 0000000000..1d9dd91f12 --- /dev/null +++ b/cpan/HTTP-Tiny/t/100_get.t @@ -0,0 +1,113 @@ +#!perl +# +# This file is part of HTTP-Tiny +# +# This software is copyright (c) 2011 by Christian Hansen. +# +# This is free software; you can redistribute it and/or modify it under +# the same terms as the Perl 5 programming language system itself. +# + +use strict; +use warnings; + +use File::Basename; +use Test::More 0.88; +use t::Util qw[tmpfile rewind slurp monkey_patch dir_list parse_case + hashify connect_args set_socket_source sort_headers $CRLF $LF]; + +use HTTP::Tiny; +BEGIN { monkey_patch() } + +for my $file ( dir_list("t/cases", qr/^get/ ) ) { + my $label = basename($file); + my $data = do { local (@ARGV,$/) = $file; <> }; + my ($params, $expect_req, $give_res) = split /--+\n/, $data; + my $case = parse_case($params); + + my $url = $case->{url}[0]; + my %headers = hashify( $case->{headers} ); + my %new_args = hashify( $case->{new_args} ); + + my %options; + $options{headers} = \%headers if %headers; + if ( $case->{data_cb} ) { + $main::data = ''; + $options{data_callback} = eval join "\n", @{$case->{data_cb}}; + die unless ref( $options{data_callback} ) eq 'CODE'; + } + + my $version = HTTP::Tiny->VERSION || 0; + my $agent = $new_args{agent} || "HTTP-Tiny/$version"; + + # cleanup source data + $expect_req =~ s{HTTP-Tiny/VERSION}{$agent}; + s{\n}{$CRLF}g for ($expect_req, $give_res); + + # setup mocking and test + my $res_fh = tmpfile($give_res); + my $req_fh = tmpfile(); + + my $http = HTTP::Tiny->new(%new_args); + set_socket_source($req_fh, $res_fh); + + (my $url_basename = $url) =~ s{.*/}{}; + + my @call_args = %options ? ($url, \%options) : ($url); + my $response = $http->get(@call_args); + + my ($got_host, $got_port) = connect_args(); + my ($exp_host, $exp_port) = ( + ($new_args{proxy} || $url ) =~ m{^http://([^:/]+?):?(\d*)/}g + ); + $exp_host ||= 'localhost'; + $exp_port ||= 80; + + my $got_req = slurp($req_fh); + + is ($got_host, $exp_host, "$label host $exp_host"); + is ($got_port, $exp_port, "$label port $exp_port"); + is( sort_headers($got_req), sort_headers($expect_req), "$label request data"); + + my ($rc) = $give_res =~ m{\S+\s+(\d+)}g; + # maybe override + $rc = $case->{expected_rc}[0] if defined $case->{expected_rc}; + + is( $response->{status}, $rc, "$label response code $rc" ) + or diag $response->{content}; + + if ( substr($rc,0,1) eq '2' ) { + ok( $response->{success}, "$label success flag true" ); + } + else { + ok( ! $response->{success}, "$label success flag false" ); + } + + if (defined $case->{expected_headers}) { + my %expected = hashify( $case->{expected_headers} ); + is_deeply($response->{headers}, \%expected, "$label expected headers"); + } + + my $check_expected = $case->{expected_like} + ? sub { + my ($text, $msg) = @_; + like( $text, "/".$case->{expected_like}[0]."/", $msg ); + } + : sub { + my ($text, $msg) = @_; + my $exp_content = + $case->{expected} ? join("$CRLF", @{$case->{expected}}, '') : ''; + is ( $text, $exp_content, $msg ); + } + ; + + if ( $options{data_callback} ) { + $check_expected->( $main::data, "$label cb got content" ); + is ( $response->{content}, '', "$label resp content empty" ); + } + else { + $check_expected->( $response->{content}, "$label content" ); + } +} + +done_testing; diff --git a/cpan/HTTP-Tiny/t/110_mirror.t b/cpan/HTTP-Tiny/t/110_mirror.t new file mode 100644 index 0000000000..34e0c90666 --- /dev/null +++ b/cpan/HTTP-Tiny/t/110_mirror.t @@ -0,0 +1,98 @@ +#!perl +# +# This file is part of HTTP-Tiny +# +# This software is copyright (c) 2011 by Christian Hansen. +# +# This is free software; you can redistribute it and/or modify it under +# the same terms as the Perl 5 programming language system itself. +# + +use strict; +use warnings; + +use File::Basename; +use Test::More 0.88; +use t::Util qw[tmpfile rewind slurp monkey_patch dir_list parse_case + set_socket_source sort_headers $CRLF $LF]; +use HTTP::Tiny; +use File::Temp qw/tempdir/; + +BEGIN { monkey_patch() } + +my $tempdir = tempdir( TMPDIR => 1, CLEANUP => 1 ); +my $tempfile = $tempdir . "/tempfile.txt"; + +my $known_epoch = 760233600; +my $day = 24*3600; + +my %timestamp = ( + 'modified.txt' => $known_epoch - 2 * $day, + 'not-modified.txt' => $known_epoch - 2 * $day, +); + +for my $file ( dir_list("t/cases", qr/^mirror/ ) ) { + unlink $tempfile; + my $data = do { local (@ARGV,$/) = $file; <> }; + my ($params, $expect_req, $give_res) = split /--+\n/, $data; + # cleanup source data + my $version = HTTP::Tiny->VERSION || 0; + $expect_req =~ s{VERSION}{$version}; + s{\n}{$CRLF}g for ($expect_req, $give_res); + + # figure out what request to make + my $case = parse_case($params); + my $url = $case->{url}->[0]; + my %options; + + my %headers; + for my $line ( @{ $case->{headers} } ) { + my ($k,$v) = ($line =~ m{^([^:]+): (.*)$}g); + $headers{$k} = $v; + } + $options{headers} = \%headers if %headers; + + # maybe create a file + (my $url_basename = $url) =~ s{.*/}{}; + if ( my $mtime = $timestamp{$url_basename} ) { + open my $fh, ">", $tempfile; + close $fh; + utime $mtime, $mtime, $tempfile; + } + + # setup mocking and test + my $res_fh = tmpfile($give_res); + my $req_fh = tmpfile(); + + my $http = HTTP::Tiny->new; + set_socket_source($req_fh, $res_fh); + + my @call_args = %options ? ($url, $tempfile, \%options) : ($url, $tempfile); + my $response = $http->mirror(@call_args); + + my $got_req = slurp($req_fh); + + my $label = basename($file); + + is( sort_headers($got_req), sort_headers($expect_req), "$label request" ); + + my ($rc) = $give_res =~ m{\S+\s+(\d+)}g; + is( $response->{status}, $rc, "$label response code $rc" ) + or diag $response->{content}; + + if ( substr($rc,0,1) eq '2' ) { + ok( $response->{success}, "$label success flag true" ); + ok( -e $tempfile, "$label file created" ); + } + elsif ( $rc eq '304' ) { + ok( $response->{success}, "$label success flag true" ); + is( (stat($tempfile))[9], $timestamp{$url_basename}, + "$label file not overwritten" ); + } + else { + ok( ! $response->{success}, "$label success flag false" ); + ok( ! -e $tempfile, "$label file not created" ); + } +} + +done_testing; diff --git a/cpan/HTTP-Tiny/t/120_put.t b/cpan/HTTP-Tiny/t/120_put.t new file mode 100644 index 0000000000..07fa27dc5c --- /dev/null +++ b/cpan/HTTP-Tiny/t/120_put.t @@ -0,0 +1,82 @@ +#!perl +# +# This file is part of HTTP-Tiny +# +# This software is copyright (c) 2011 by Christian Hansen. +# +# This is free software; you can redistribute it and/or modify it under +# the same terms as the Perl 5 programming language system itself. +# + +use strict; +use warnings; + +use File::Basename; +use Test::More 0.88; +use t::Util qw[tmpfile rewind slurp monkey_patch dir_list parse_case + set_socket_source sort_headers $CRLF $LF]; +use HTTP::Tiny; +BEGIN { monkey_patch() } + +for my $file ( dir_list("t/cases", qr/^put/ ) ) { + my $data = do { local (@ARGV,$/) = $file; <> }; + my ($params, $expect_req, $give_res) = split /--+\n/, $data; + # cleanup source data + my $version = HTTP::Tiny->VERSION || 0; + $expect_req =~ s{VERSION}{$version}; + s{\n}{$CRLF}g for ($expect_req, $give_res); + + # figure out what request to make + my $case = parse_case($params); + my $url = $case->{url}[0]; + my %options; + + my %headers; + for my $line ( @{ $case->{headers} } ) { + my ($k,$v) = ($line =~ m{^([^:]+): (.*)$}g); + $headers{$k} = $v; + } + $options{headers} = \%headers if %headers; + + if ( $case->{content} ) { + $options{content} = $case->{content}[0]; + } + elsif ( $case->{content_cb} ) { + $options{content} = eval join "\n", @{$case->{content_cb}}; + } + + if ( $case->{trailer_cb} ) { + $options{trailer_callback} = eval join "\n", @{$case->{trailer_cb}}; + } + + # setup mocking and test + my $res_fh = tmpfile($give_res); + my $req_fh = tmpfile(); + + my $http = HTTP::Tiny->new; + set_socket_source($req_fh, $res_fh); + + (my $url_basename = $url) =~ s{.*/}{}; + + my @call_args = %options ? ($url, \%options) : ($url); + my $response = $http->request('PUT',@call_args); + + my $got_req = slurp($req_fh); + + my $label = basename($file); + + is( sort_headers($got_req), sort_headers($expect_req), "$label request" ); + + my ($rc) = $give_res =~ m{\S+\s+(\d+)}g; + is( $response->{status}, $rc, "$label response code $rc" ) + or diag $response->{content}; + + if ( substr($rc,0,1) eq '2' ) { + ok( $response->{success}, "$label success flag true" ); + } + else { + ok( ! $response->{success}, "$label success flag false" ); + } +} + +done_testing; diff --git a/cpan/HTTP-Tiny/t/130_redirect.t b/cpan/HTTP-Tiny/t/130_redirect.t new file mode 100644 index 0000000000..9848321043 --- /dev/null +++ b/cpan/HTTP-Tiny/t/130_redirect.t @@ -0,0 +1,78 @@ +#!perl +# +# This file is part of HTTP-Tiny +# +# This software is copyright (c) 2011 by Christian Hansen. +# +# This is free software; you can redistribute it and/or modify it under +# the same terms as the Perl 5 programming language system itself. +# + +use strict; +use warnings; + +use File::Basename; +use Test::More 0.88; +use t::Util qw[tmpfile rewind slurp monkey_patch dir_list parse_case + hashify connect_args clear_socket_source set_socket_source sort_headers + $CRLF $LF]; + +use HTTP::Tiny; +BEGIN { monkey_patch() } + +for my $file ( dir_list("t/cases", qr/^redirect/ ) ) { + my $label = basename($file); + my $data = do { local (@ARGV,$/) = $file; <> }; + my ($params, @case_pairs) = split /--+\n/, $data; + my $case = parse_case($params); + + my $url = $case->{url}[0]; + my $method = $case->{method}[0] || 'GET'; + my %headers = hashify( $case->{headers} ); + my %new_args = hashify( $case->{new_args} ); + + my %options; + $options{headers} = \%headers if %headers; + my $call_args = %options ? [$method, $url, \%options] : [$method, $url]; + + my $version = HTTP::Tiny->VERSION || 0; + my $agent = $new_args{agent} || "HTTP-Tiny/$version"; + + my (@socket_pairs); + while ( @case_pairs ) { + my ($expect_req, $give_res) = splice( @case_pairs, 0, 2 ); + # cleanup source data + $expect_req =~ s{HTTP-Tiny/VERSION}{$agent}; + s{\n}{$CRLF}g for ($expect_req, $give_res); + + # setup mocking and test + my $req_fh = tmpfile(); + my $res_fh = tmpfile($give_res); + + push @socket_pairs, [$req_fh, $res_fh, $expect_req]; + } + + clear_socket_source(); + set_socket_source(@$_) for @socket_pairs; + + my $http = HTTP::Tiny->new(%new_args); + my $response = $http->request(@$call_args); + + my $calls = 0 + + (defined($new_args{max_redirect}) ? $new_args{max_redirect} : 5); + + for my $i ( 0 .. $calls ) { + last unless @socket_pairs; + my ($req_fh, $res_fh, $expect_req) = @{ shift @socket_pairs }; + my $got_req = slurp($req_fh); + is( sort_headers($got_req), sort_headers($expect_req), "$label request ($i)"); + $i++; + } + + my $exp_content = $case->{expected} + ? join("$CRLF", @{$case->{expected}}) : ''; + + is ( $response->{content}, $exp_content, "$label content" ); +} + +done_testing; diff --git a/cpan/HTTP-Tiny/t/Util.pm b/cpan/HTTP-Tiny/t/Util.pm new file mode 100644 index 0000000000..f6c5f321de --- /dev/null +++ b/cpan/HTTP-Tiny/t/Util.pm @@ -0,0 +1,177 @@ +# +# This file is part of HTTP-Tiny +# +# This software is copyright (c) 2011 by Christian Hansen. +# +# This is free software; you can redistribute it and/or modify it under +# the same terms as the Perl 5 programming language system itself. +# +package t::Util; + +use strict; +use warnings; + +use IO::File q[SEEK_SET]; +use IO::Dir; + +BEGIN { + our @EXPORT_OK = qw( + rewind + tmpfile + dir_list + slurp + parse_case + hashify + sort_headers + connect_args + clear_socket_source + set_socket_source + monkey_patch + $CRLF + $LF + ); + + require Exporter; + *import = \&Exporter::import; +} + +our $CRLF = "\x0D\x0A"; +our $LF = "\x0A"; + +sub rewind(*) { + seek($_[0], 0, SEEK_SET) + || die(qq/Couldn't rewind file handle: '$!'/); +} + +sub tmpfile { + my $fh = IO::File->new_tmpfile + || die(qq/Couldn't create a new temporary file: '$!'/); + + binmode($fh) + || die(qq/Couldn't binmode temporary file handle: '$!'/); + + if (@_) { + print({$fh} @_) + || die(qq/Couldn't write to temporary file handle: '$!'/); + + seek($fh, 0, SEEK_SET) + || die(qq/Couldn't rewind temporary file handle: '$!'/); + } + + return $fh; +} + +sub dir_list { + my ($dir, $filter) = @_; + $filter ||= qr/./; + my $d = IO::Dir->new($dir) + or return; + return map { "$dir/$_" } sort grep { /$filter/ } grep { /^[^.]/ } $d->read; +} + +sub slurp (*) { + my ($fh) = @_; + + rewind($fh); + + binmode($fh) + || die(qq/Couldn't binmode file handle: '$!'/); + + my $exp = -s $fh; + my $buf = do { local $/; <$fh> }; + my $got = length $buf; + + ($exp == $got) + || die(qq[I/O read mismatch (expexted: $exp got: $got)]); + + return $buf; +} + +sub parse_case { + my ($case) = @_; + my %args; + my $key = ''; + for my $line ( split "\n", $case ) { + chomp $line; + if ( substr($line,0,1) eq q{ } ) { + $line =~ s/^\s+//; + push @{$args{$key}}, $line; + } + else { + $key = $line; + } + } + return \%args; +} + +sub hashify { + my ($lines) = @_; + return unless $lines; + my %hash; + for my $line ( @$lines ) { + my ($k,$v) = ($line =~ m{^([^:]+): (.*)$}g); + $hash{$k} = [ $hash{$k} ] if exists $hash{$k} && ref $hash{$k} ne 'ARRAY'; + if ( ref($hash{$k}) eq 'ARRAY' ) { + push @{$hash{$k}}, $v; + } + else { + $hash{$k} = $v; + } + } + return %hash; +} + +sub sort_headers { + my ($text) = shift; + my @lines = split /$CRLF/, $text; + my $request = shift(@lines) || ''; + my @headers; + while (my $line = shift @lines) { + last unless length $line; + push @headers, $line; + } + @headers = sort @headers; + return join($CRLF, $request, @headers, '', @lines); +} + +{ + my (@req_fh, @res_fh, $monkey_host, $monkey_port); + + sub clear_socket_source { + @req_fh = (); + @res_fh = (); + } + + sub set_socket_source { + my ($req_fh, $res_fh) = @_; + push @req_fh, $req_fh; + push @res_fh, $res_fh; + } + + sub connect_args { return ($monkey_host, $monkey_port) } + + sub monkey_patch { + no warnings qw/redefine once/; + *HTTP::Tiny::Handle::can_read = sub {1}; + *HTTP::Tiny::Handle::can_write = sub {1}; + *HTTP::Tiny::Handle::connect = sub { + my ($self, $scheme, $host, $port) = @_; + $self->{host} = $monkey_host = $host; + $self->{port} = $monkey_port = $port; + $self->{fh} = shift @req_fh; + return $self; + }; + my $original_write_request = \&HTTP::Tiny::Handle::write_request; + *HTTP::Tiny::Handle::write_request = sub { + my ($self, $request) = @_; + $original_write_request->($self, $request); + $self->{fh} = shift @res_fh; + }; + *HTTP::Tiny::Handle::close = sub { 1 }; # don't close our temps + } +} + +1; + + +# vim: et ts=4 sts=4 sw=4: diff --git a/cpan/HTTP-Tiny/t/cases/get-01.txt b/cpan/HTTP-Tiny/t/cases/get-01.txt new file mode 100644 index 0000000000..e03fb3876c --- /dev/null +++ b/cpan/HTTP-Tiny/t/cases/get-01.txt @@ -0,0 +1,17 @@ +url + http://example.com/index.html +expected + abcdefghijklmnopqrstuvwxyz1234567890abcdef +---------- +GET /index.html HTTP/1.1 +Host: example.com +Connection: close +User-Agent: HTTP-Tiny/VERSION + +---------- +HTTP/1.1 200 OK +Date: Thu, 03 Feb 1994 00:00:00 GMT +Content-Type: text/plain +Content-Length: 44 + +abcdefghijklmnopqrstuvwxyz1234567890abcdef diff --git a/cpan/HTTP-Tiny/t/cases/get-02.txt b/cpan/HTTP-Tiny/t/cases/get-02.txt new file mode 100644 index 0000000000..4b540f4462 --- /dev/null +++ b/cpan/HTTP-Tiny/t/cases/get-02.txt @@ -0,0 +1,22 @@ +url + http://example.com/index.html +expected + abcdefghijklmnopqrstuvwxyz1234567890abcdef +headers + Accept: */* + X-Custom: This is a custom header +---------- +GET /index.html HTTP/1.1 +Host: example.com +Accept: */* +Connection: close +User-Agent: HTTP-Tiny/VERSION +X-Custom: This is a custom header + +---------- +HTTP/1.1 200 OK +Date: Thu, 03 Feb 1994 00:00:00 GMT +Content-Type: text/plain +Content-Length: 44 + +abcdefghijklmnopqrstuvwxyz1234567890abcdef diff --git a/cpan/HTTP-Tiny/t/cases/get-03.txt b/cpan/HTTP-Tiny/t/cases/get-03.txt new file mode 100644 index 0000000000..e5eed63135 --- /dev/null +++ b/cpan/HTTP-Tiny/t/cases/get-03.txt @@ -0,0 +1,13 @@ +url + http://example.com/missing.html +---------- +GET /missing.html HTTP/1.1 +Host: example.com +Connection: close +User-Agent: HTTP-Tiny/VERSION + +---------- +HTTP/1.1 404 Not Found +Date: Thu, 03 Feb 1994 00:00:00 GMT +Content-Length: 0 + diff --git a/cpan/HTTP-Tiny/t/cases/get-04.txt b/cpan/HTTP-Tiny/t/cases/get-04.txt new file mode 100644 index 0000000000..71698f9fe9 --- /dev/null +++ b/cpan/HTTP-Tiny/t/cases/get-04.txt @@ -0,0 +1,17 @@ +url + http://example.com:9000/index.html +expected + abcdefghijklmnopqrstuvwxyz1234567890abcdef +---------- +GET /index.html HTTP/1.1 +Host: example.com:9000 +Connection: close +User-Agent: HTTP-Tiny/VERSION + +---------- +HTTP/1.1 200 OK +Date: Thu, 03 Feb 1994 00:00:00 GMT +Content-Type: text/plain +Content-Length: 44 + +abcdefghijklmnopqrstuvwxyz1234567890abcdef diff --git a/cpan/HTTP-Tiny/t/cases/get-05.txt b/cpan/HTTP-Tiny/t/cases/get-05.txt new file mode 100644 index 0000000000..b689aaab0c --- /dev/null +++ b/cpan/HTTP-Tiny/t/cases/get-05.txt @@ -0,0 +1,21 @@ +url + http://example.com/chunked.html +expected + abcdefghijklmnopqrstuvwxyz1234567890abcdef +---------- +GET /chunked.html HTTP/1.1 +Host: example.com +Connection: close +User-Agent: HTTP-Tiny/VERSION + +---------- +HTTP/1.1 200 OK +Date: Thu, 03 Feb 1994 00:00:00 GMT +Content-Type: text/plain +Transfer-Encoding: chunked + +2C +abcdefghijklmnopqrstuvwxyz1234567890abcdef + +0 + diff --git a/cpan/HTTP-Tiny/t/cases/get-06.txt b/cpan/HTTP-Tiny/t/cases/get-06.txt new file mode 100644 index 0000000000..131bb58e3b --- /dev/null +++ b/cpan/HTTP-Tiny/t/cases/get-06.txt @@ -0,0 +1,19 @@ +url + http://example.com/cb.html +expected + abcdefghijklmnopqrstuvwxyz1234567890abcdef +data_cb + sub { $main::data .= $_[0] } +---------- +GET /cb.html HTTP/1.1 +Host: example.com +Connection: close +User-Agent: HTTP-Tiny/VERSION + +---------- +HTTP/1.1 200 OK +Date: Thu, 03 Feb 1994 00:00:00 GMT +Content-Type: text/plain +Content-Length: 44 + +abcdefghijklmnopqrstuvwxyz1234567890abcdef diff --git a/cpan/HTTP-Tiny/t/cases/get-07.txt b/cpan/HTTP-Tiny/t/cases/get-07.txt new file mode 100644 index 0000000000..dec18fdf80 --- /dev/null +++ b/cpan/HTTP-Tiny/t/cases/get-07.txt @@ -0,0 +1,19 @@ +new_args + proxy: http://proxy.example.com:8080/ +url + http://example.com/index.html +expected + abcdefghijklmnopqrstuvwxyz1234567890abcdef +---------- +GET http://example.com/index.html HTTP/1.1 +Host: example.com +Connection: close +User-Agent: HTTP-Tiny/VERSION + +---------- +HTTP/1.1 200 OK +Date: Thu, 03 Feb 1994 00:00:00 GMT +Content-Type: text/plain +Content-Length: 44 + +abcdefghijklmnopqrstuvwxyz1234567890abcdef diff --git a/cpan/HTTP-Tiny/t/cases/get-08.txt b/cpan/HTTP-Tiny/t/cases/get-08.txt new file mode 100644 index 0000000000..3044db1c78 --- /dev/null +++ b/cpan/HTTP-Tiny/t/cases/get-08.txt @@ -0,0 +1,21 @@ +new_args + max_size: 26 +url + http://example.com/index.html +expected_rc + 599 +expected_like + Size of response body exceeds the maximum allowed of 26 +---------- +GET /index.html HTTP/1.1 +Host: example.com +Connection: close +User-Agent: HTTP-Tiny/VERSION + +---------- +HTTP/1.1 200 OK +Date: Thu, 03 Feb 1994 00:00:00 GMT +Content-Type: text/plain +Content-Length: 44 + +abcdefghijklmnopqrstuvwxyz1234567890abcdef diff --git a/cpan/HTTP-Tiny/t/cases/get-09.txt b/cpan/HTTP-Tiny/t/cases/get-09.txt new file mode 100644 index 0000000000..0d5eb5dbe2 --- /dev/null +++ b/cpan/HTTP-Tiny/t/cases/get-09.txt @@ -0,0 +1,16 @@ +url + http://example.com/index.html +expected + abcdefghijklmnopqrstuvwxyz1234567890abcdef +---------- +GET /index.html HTTP/1.1 +Host: example.com +Connection: close +User-Agent: HTTP-Tiny/VERSION + +---------- +HTTP/1.1 200 OK +Date: Thu, 03 Feb 1994 00:00:00 GMT +Content-Type: text/plain + +abcdefghijklmnopqrstuvwxyz1234567890abcdef diff --git a/cpan/HTTP-Tiny/t/cases/get-10.txt b/cpan/HTTP-Tiny/t/cases/get-10.txt new file mode 100644 index 0000000000..23c0163547 --- /dev/null +++ b/cpan/HTTP-Tiny/t/cases/get-10.txt @@ -0,0 +1,21 @@ +url + http://example.com/chunked.html +expected + abcdefghijklmnopqrstuvwxyz1234567890abcdef +---------- +GET /chunked.html HTTP/1.1 +Host: example.com +Connection: close +User-Agent: HTTP-Tiny/VERSION + +---------- +HTTP/1.1 200 OK +Date: Thu, 03 Feb 1994 00:00:00 GMT +Content-Type: text/plain +Transfer-Encoding: CHUNKED + +2C +abcdefghijklmnopqrstuvwxyz1234567890abcdef + +0 + diff --git a/cpan/HTTP-Tiny/t/cases/get-11.txt b/cpan/HTTP-Tiny/t/cases/get-11.txt new file mode 100644 index 0000000000..dca6d14ce1 --- /dev/null +++ b/cpan/HTTP-Tiny/t/cases/get-11.txt @@ -0,0 +1,22 @@ +url + http://example.com/chunked.html +expected + abcdefghijklmnopqrstuvwxyz1234567890abcdef +---------- +GET /chunked.html HTTP/1.1 +Host: example.com +Connection: close +User-Agent: HTTP-Tiny/VERSION + +---------- +HTTP/1.1 200 OK +Date: Thu, 03 Feb 1994 00:00:00 GMT +Content-Type: text/plain +Content-Length: 1024 +Transfer-Encoding: chunked + +2C +abcdefghijklmnopqrstuvwxyz1234567890abcdef + +0 + diff --git a/cpan/HTTP-Tiny/t/cases/get-12.txt b/cpan/HTTP-Tiny/t/cases/get-12.txt new file mode 100644 index 0000000000..9cf2bf72e1 --- /dev/null +++ b/cpan/HTTP-Tiny/t/cases/get-12.txt @@ -0,0 +1,17 @@ +url + http:///index.html +expected + abcdefghijklmnopqrstuvwxyz1234567890abcdef +---------- +GET /index.html HTTP/1.1 +Host: localhost +Connection: close +User-Agent: HTTP-Tiny/VERSION + +---------- +HTTP/1.1 200 OK +Date: Thu, 03 Feb 1994 00:00:00 GMT +Content-Type: text/plain +Content-Length: 44 + +abcdefghijklmnopqrstuvwxyz1234567890abcdef diff --git a/cpan/HTTP-Tiny/t/cases/get-13.txt b/cpan/HTTP-Tiny/t/cases/get-13.txt new file mode 100644 index 0000000000..7e437559f7 --- /dev/null +++ b/cpan/HTTP-Tiny/t/cases/get-13.txt @@ -0,0 +1,21 @@ +url + http://example.com/chunked.html +expected + abcdefghijklmnopqrstuvwxyz1234567890abcdef +---------- +GET /chunked.html HTTP/1.1 +Host: example.com +Connection: close +User-Agent: HTTP-Tiny/VERSION + +---------- +HTTP/1.1 200 OK +Date: Thu, 03 Feb 1994 00:00:00 GMT +Content-Type: text/plain +Transfer-Encoding: chunked + +2C; this_extension=foo +abcdefghijklmnopqrstuvwxyz1234567890abcdef + +0 + diff --git a/cpan/HTTP-Tiny/t/cases/get-14.txt b/cpan/HTTP-Tiny/t/cases/get-14.txt new file mode 100644 index 0000000000..e232aa2a33 --- /dev/null +++ b/cpan/HTTP-Tiny/t/cases/get-14.txt @@ -0,0 +1,22 @@ +url + http://example.com/index.html +expected + abcdefghijklmnopqrstuvwxyz1234567890abcdef +headers + X-Foo: one + X-Foo: two +---------- +GET /index.html HTTP/1.1 +Host: example.com +Connection: close +User-Agent: HTTP-Tiny/VERSION +X-Foo: one +X-Foo: two + +---------- +HTTP/1.1 200 OK +Date: Thu, 03 Feb 1994 00:00:00 GMT +Content-Type: text/plain +Content-Length: 44 + +abcdefghijklmnopqrstuvwxyz1234567890abcdef diff --git a/cpan/HTTP-Tiny/t/cases/get-15.txt b/cpan/HTTP-Tiny/t/cases/get-15.txt new file mode 100644 index 0000000000..0bd5065d71 --- /dev/null +++ b/cpan/HTTP-Tiny/t/cases/get-15.txt @@ -0,0 +1,17 @@ +url + http://example.com/index.html +expected + abcdefghijklmnopqrstuvwxyz1234567890abcdef +---------- +GET /index.html HTTP/1.1 +Host: example.com +Connection: close +User-Agent: HTTP-Tiny/VERSION + +---------- +HTTP/01.01 200 OK +Date: Thu, 03 Feb 1994 00:00:00 GMT +Content-Type: text/plain +Content-Length: 44 + +abcdefghijklmnopqrstuvwxyz1234567890abcdef diff --git a/cpan/HTTP-Tiny/t/cases/get-16.txt b/cpan/HTTP-Tiny/t/cases/get-16.txt new file mode 100644 index 0000000000..e17076de18 --- /dev/null +++ b/cpan/HTTP-Tiny/t/cases/get-16.txt @@ -0,0 +1,19 @@ +url + http://example.com/index.html +expected_rc + 599 +expected_like + Malformed Status-Line +---------- +GET /index.html HTTP/1.1 +Host: example.com +Connection: close +User-Agent: HTTP-Tiny/VERSION + +---------- +HTTP/0 200 OK +Date: Thu, 03 Feb 1994 00:00:00 GMT +Content-Type: text/plain +Content-Length: 44 + +abcdefghijklmnopqrstuvwxyz1234567890abcdef diff --git a/cpan/HTTP-Tiny/t/cases/get-17.txt b/cpan/HTTP-Tiny/t/cases/get-17.txt new file mode 100644 index 0000000000..e5d3d12eeb --- /dev/null +++ b/cpan/HTTP-Tiny/t/cases/get-17.txt @@ -0,0 +1,19 @@ +url + http://example.com/index.html +expected_rc + 599 +expected_like + Unsupported HTTP protocol +---------- +GET /index.html HTTP/1.1 +Host: example.com +Connection: close +User-Agent: HTTP-Tiny/VERSION + +---------- +HTTP/1.2 200 OK +Date: Thu, 03 Feb 1994 00:00:00 GMT +Content-Type: text/plain +Content-Length: 44 + +abcdefghijklmnopqrstuvwxyz1234567890abcdef diff --git a/cpan/HTTP-Tiny/t/cases/get-18.txt b/cpan/HTTP-Tiny/t/cases/get-18.txt new file mode 100644 index 0000000000..f46f48cea3 --- /dev/null +++ b/cpan/HTTP-Tiny/t/cases/get-18.txt @@ -0,0 +1,36 @@ +url + http://example.com/chunked.html +expected + abcdefghijklmnopqrstuvwxyz1234567890abcdef +expected_headers + transfer-encoding: chunked + x-a: Foo + x-a: Bar + x-a: Baz + x-b: Foo + x-b: Bar + x-b: Baz + x-c: Foo + +---------- +GET /chunked.html HTTP/1.1 +Host: example.com +Connection: close +User-Agent: HTTP-Tiny/VERSION + +---------- +HTTP/1.1 200 OK +Transfer-Encoding: chunked +X-A: Foo +X-B: Foo +X-B: Bar + +2C +abcdefghijklmnopqrstuvwxyz1234567890abcdef + +0 +X-A: Bar +X-A: Baz +X-B: Baz +X-C: Foo + diff --git a/cpan/HTTP-Tiny/t/cases/get-19.txt b/cpan/HTTP-Tiny/t/cases/get-19.txt new file mode 100644 index 0000000000..2ebb9a84b4 --- /dev/null +++ b/cpan/HTTP-Tiny/t/cases/get-19.txt @@ -0,0 +1,22 @@ +url + http://example.com/chunked.html +expected + abcdefghijklmnopqrstuvwxyz1234567890abcdef +---------- +GET /chunked.html HTTP/1.1 +Host: example.com +Connection: close +User-Agent: HTTP-Tiny/VERSION + +---------- +HTTP/1.1 200 OK +Date: Thu, 03 Feb 1994 00:00:00 GMT +Content-Type: text/plain +Transfer-Encoding: wacky +Transfer-Encoding: chunked + +2C +abcdefghijklmnopqrstuvwxyz1234567890abcdef + +0 + diff --git a/cpan/HTTP-Tiny/t/cases/get-20.txt b/cpan/HTTP-Tiny/t/cases/get-20.txt new file mode 100644 index 0000000000..8873793665 --- /dev/null +++ b/cpan/HTTP-Tiny/t/cases/get-20.txt @@ -0,0 +1,21 @@ +url + http://example.com/index.html +headers + connection: X-Foo + X-Foo: bar +expected + abcdefghijklmnopqrstuvwxyz1234567890abcdef +---------- +GET /index.html HTTP/1.1 +Host: example.com +Connection: close +X-Foo: bar +User-Agent: HTTP-Tiny/VERSION + +---------- +HTTP/1.1 200 OK +Date: Thu, 03 Feb 1994 00:00:00 GMT +Content-Type: text/plain +Content-Length: 44 + +abcdefghijklmnopqrstuvwxyz1234567890abcdef diff --git a/cpan/HTTP-Tiny/t/cases/get-21.txt b/cpan/HTTP-Tiny/t/cases/get-21.txt new file mode 100644 index 0000000000..df207f6c02 --- /dev/null +++ b/cpan/HTTP-Tiny/t/cases/get-21.txt @@ -0,0 +1,23 @@ +url + http://example.com/index.html +expected + abcdefghijklmnopqrstuvwxyz1234567890abcdef +expected_rc + 200 +---------- +GET /index.html HTTP/1.1 +Host: example.com +Connection: close +User-Agent: HTTP-Tiny/VERSION + +---------- +HTTP/1.1 100 Continue + +HTTP/1.1 110 Arbitrary 1XX status code + +HTTP/1.1 200 OK +Date: Thu, 03 Feb 1994 00:00:00 GMT +Content-Type: text/plain +Content-Length: 44 + +abcdefghijklmnopqrstuvwxyz1234567890abcdef diff --git a/cpan/HTTP-Tiny/t/cases/mirror-01.txt b/cpan/HTTP-Tiny/t/cases/mirror-01.txt new file mode 100644 index 0000000000..5ac0eb5cad --- /dev/null +++ b/cpan/HTTP-Tiny/t/cases/mirror-01.txt @@ -0,0 +1,18 @@ +url + http://example.com/modified.txt +---------- +GET /modified.txt HTTP/1.1 +Host: example.com +Connection: close +If-Modified-Since: Tue, 01 Feb 1994 00:00:00 GMT +User-Agent: HTTP-Tiny/VERSION + +---------- +HTTP/1.1 200 OK +Date: Thu, 03 Feb 1994 00:00:00 GMT +Last-Modified: Wed, 02 Feb 1994 00:00:00 GMT +Content-Type: text/plain +Content-Length: 42 + +abcdefghijklmnopqrstuvwxyz1234567890abcdef + diff --git a/cpan/HTTP-Tiny/t/cases/mirror-02.txt b/cpan/HTTP-Tiny/t/cases/mirror-02.txt new file mode 100644 index 0000000000..4799a2e777 --- /dev/null +++ b/cpan/HTTP-Tiny/t/cases/mirror-02.txt @@ -0,0 +1,14 @@ +url + http://example.com/not-modified.txt +---------- +GET /not-modified.txt HTTP/1.1 +Host: example.com +Connection: close +If-Modified-Since: Tue, 01 Feb 1994 00:00:00 GMT +User-Agent: HTTP-Tiny/VERSION + +---------- +HTTP/1.1 304 Not Modified +Date: Thu, 03 Feb 1994 00:00:00 GMT +Last-Modified: Tue, 01 Feb 1994 00:00:00 GMT + diff --git a/cpan/HTTP-Tiny/t/cases/mirror-03.txt b/cpan/HTTP-Tiny/t/cases/mirror-03.txt new file mode 100644 index 0000000000..711710d90c --- /dev/null +++ b/cpan/HTTP-Tiny/t/cases/mirror-03.txt @@ -0,0 +1,17 @@ +url + http://example.com/new.txt +---------- +GET /new.txt HTTP/1.1 +Host: example.com +Connection: close +User-Agent: HTTP-Tiny/VERSION + +---------- +HTTP/1.1 200 OK +Date: Thu, 03 Feb 1994 00:00:00 GMT +Last-Modified: Tue, 01 Feb 1994 00:00:00 GMT +Content-Type: text/plain +Content-Length: 42 + +abcdefghijklmnopqrstuvwxyz1234567890abcdef + diff --git a/cpan/HTTP-Tiny/t/cases/mirror-04.txt b/cpan/HTTP-Tiny/t/cases/mirror-04.txt new file mode 100644 index 0000000000..9beeeb3019 --- /dev/null +++ b/cpan/HTTP-Tiny/t/cases/mirror-04.txt @@ -0,0 +1,13 @@ +url + http://example.com/missing.txt +---------- +GET /missing.txt HTTP/1.1 +Host: example.com +Connection: close +User-Agent: HTTP-Tiny/VERSION + +---------- +HTTP/1.1 404 Not Found +Date: Thu, 03 Feb 1994 00:00:00 GMT +Content-Length: 0 + diff --git a/cpan/HTTP-Tiny/t/cases/mirror-05.txt b/cpan/HTTP-Tiny/t/cases/mirror-05.txt new file mode 100644 index 0000000000..f1f3573627 --- /dev/null +++ b/cpan/HTTP-Tiny/t/cases/mirror-05.txt @@ -0,0 +1,20 @@ +url + http://example.com/modified.txt +headers + if-modified-since: Tue, 01 Feb 1994 12:00:00 GMT +---------- +GET /modified.txt HTTP/1.1 +Host: example.com +Connection: close +If-Modified-Since: Tue, 01 Feb 1994 12:00:00 GMT +User-Agent: HTTP-Tiny/VERSION + +---------- +HTTP/1.1 200 OK +Date: Thu, 03 Feb 1994 00:00:00 GMT +Last-Modified: Wed, 02 Feb 1994 00:00:00 GMT +Content-Type: text/plain +Content-Length: 42 + +abcdefghijklmnopqrstuvwxyz1234567890abcdef + diff --git a/cpan/HTTP-Tiny/t/cases/put-01.txt b/cpan/HTTP-Tiny/t/cases/put-01.txt new file mode 100644 index 0000000000..b8d6286fa1 --- /dev/null +++ b/cpan/HTTP-Tiny/t/cases/put-01.txt @@ -0,0 +1,22 @@ +url + http://example.com/new.txt +headers + Content-Type: text/plain +content + abcdefghijklmnopqrstuvwxyz1234567890abcdef +---------- +PUT /new.txt HTTP/1.1 +Host: example.com +Connection: close +User-Agent: HTTP-Tiny/VERSION +Content-Type: text/plain +Content-Length: 42 + +abcdefghijklmnopqrstuvwxyz1234567890abcdef + +---------- +HTTP/1.1 201 Created +Date: Thu, 03 Feb 1994 00:00:00 GMT +Location: http://example.com/new.txt +Content-Length: 0 + diff --git a/cpan/HTTP-Tiny/t/cases/put-02.txt b/cpan/HTTP-Tiny/t/cases/put-02.txt new file mode 100644 index 0000000000..04d267563b --- /dev/null +++ b/cpan/HTTP-Tiny/t/cases/put-02.txt @@ -0,0 +1,24 @@ +url + http://example.com/callback.txt +headers + Content-Type: text/plain + Content-Length: 42 +content_cb + my @content = qq{abcdefghijklmnopqrstuvwxyz1234567890abcdef}; + sub { shift @content } +---------- +PUT /callback.txt HTTP/1.1 +Host: example.com +Connection: close +User-Agent: HTTP-Tiny/VERSION +Content-Type: text/plain +Content-Length: 42 + +abcdefghijklmnopqrstuvwxyz1234567890abcdef + +---------- +HTTP/1.1 201 Created +Date: Thu, 03 Feb 1994 00:00:00 GMT +Location: http://example.com/callback.txt +Content-Length: 0 + diff --git a/cpan/HTTP-Tiny/t/cases/put-03.txt b/cpan/HTTP-Tiny/t/cases/put-03.txt new file mode 100644 index 0000000000..99fe188f4e --- /dev/null +++ b/cpan/HTTP-Tiny/t/cases/put-03.txt @@ -0,0 +1,25 @@ +url + http://example.com/chunked.txt +headers + Content-Type: text/plain +content_cb + my @content = qq{abcdefghijklmnopqrstuvwxyz1234567890abcdef}; + sub { shift @content } +---------- +PUT /chunked.txt HTTP/1.1 +Host: example.com +Connection: close +User-Agent: HTTP-Tiny/VERSION +Content-Type: text/plain +Transfer-Encoding: chunked + +2A +abcdefghijklmnopqrstuvwxyz1234567890abcdef +0 + +---------- +HTTP/1.1 201 Created +Date: Thu, 03 Feb 1994 00:00:00 GMT +Location: http://example.com/chunked.txt +Content-Length: 0 + diff --git a/cpan/HTTP-Tiny/t/cases/put-04.txt b/cpan/HTTP-Tiny/t/cases/put-04.txt new file mode 100644 index 0000000000..eeec295c80 --- /dev/null +++ b/cpan/HTTP-Tiny/t/cases/put-04.txt @@ -0,0 +1,19 @@ +url + http://example.com/new.txt +content + abcdefghijklmnopqrstuvwxyz1234567890abcdef +---------- +PUT /new.txt HTTP/1.1 +Host: example.com +Connection: close +User-Agent: HTTP-Tiny/VERSION +Content-Type: application/octet-stream +Content-Length: 42 + +abcdefghijklmnopqrstuvwxyz1234567890abcdef +---------- +HTTP/1.1 201 Created +Date: Thu, 03 Feb 1994 00:00:00 GMT +Location: http://example.com/new.txt +Content-Length: 0 + diff --git a/cpan/HTTP-Tiny/t/cases/put-05.txt b/cpan/HTTP-Tiny/t/cases/put-05.txt new file mode 100644 index 0000000000..f4bcaf18e9 --- /dev/null +++ b/cpan/HTTP-Tiny/t/cases/put-05.txt @@ -0,0 +1,27 @@ +url + http://example.com/chunked.txt +headers + Content-Type: text/plain +content_cb + my @content = qq{abcdefghijklmnopqrstuvwxyz1234567890abcdef}; + sub { shift @content } +trailer_cb + sub { return { 'x-foo' => 'bar' } } +---------- +PUT /chunked.txt HTTP/1.1 +Host: example.com +Connection: close +User-Agent: HTTP-Tiny/VERSION +Content-Type: text/plain +Transfer-Encoding: chunked + +2A +abcdefghijklmnopqrstuvwxyz1234567890abcdef +0 +X-Foo: bar +---------- +HTTP/1.1 201 Created +Date: Thu, 03 Feb 1994 00:00:00 GMT +Location: http://example.com/chunked.txt +Content-Length: 0 + diff --git a/cpan/HTTP-Tiny/t/cases/redirect-01.txt b/cpan/HTTP-Tiny/t/cases/redirect-01.txt new file mode 100644 index 0000000000..25e2ff2628 --- /dev/null +++ b/cpan/HTTP-Tiny/t/cases/redirect-01.txt @@ -0,0 +1,33 @@ +url + http://example.com/index.html +expected + abcdefghijklmnopqrstuvwxyz1234567890abcdef +---------- +GET /index.html HTTP/1.1 +Host: example.com +Connection: close +User-Agent: HTTP-Tiny/VERSION + +---------- +HTTP/1.1 302 Found +Date: Thu, 03 Feb 1994 00:00:00 GMT +Content-Type: text/html +Content-Length: 53 +Location: http://example.com/index2.html + +<a href="http://example.com/index2.html">redirect</a> + +---------- +GET /index2.html HTTP/1.1 +Host: example.com +Connection: close +User-Agent: HTTP-Tiny/VERSION + +---------- +HTTP/1.1 200 OK +Date: Thu, 03 Feb 1994 00:00:00 GMT +Content-Type: text/plain +Content-Length: 42 + +abcdefghijklmnopqrstuvwxyz1234567890abcdef + diff --git a/cpan/HTTP-Tiny/t/cases/redirect-02.txt b/cpan/HTTP-Tiny/t/cases/redirect-02.txt new file mode 100644 index 0000000000..5035879ff4 --- /dev/null +++ b/cpan/HTTP-Tiny/t/cases/redirect-02.txt @@ -0,0 +1,50 @@ +new_args + max_redirect: 0 +url + http://example.com/index.html +expected + <a href="http://example.com/index2.html">redirect</a> +---------- +GET /index.html HTTP/1.1 +Host: example.com +Connection: close +User-Agent: HTTP-Tiny/VERSION + +---------- +HTTP/1.1 302 Found +Date: Thu, 03 Feb 1994 00:00:00 GMT +Content-Type: text/html +Content-Length: 53 +Location: http://example.com/index2.html + +<a href="http://example.com/index2.html">redirect</a> + +---------- +GET /index2.html HTTP/1.1 +Host: example.com +Connection: close +User-Agent: HTTP-Tiny/VERSION + +---------- +HTTP/1.1 302 Found +Date: Thu, 03 Feb 1994 00:00:00 GMT +Content-Type: text/html +Content-Length: 53 +Location: http://example.com/index3.html + +<a href="http://example.com/index3.html">redirect</a> + +---------- +GET /index3.html HTTP/1.1 +Host: example.com +Connection: close +User-Agent: HTTP-Tiny/VERSION + +---------- +HTTP/1.1 200 OK +Date: Thu, 03 Feb 1994 00:00:00 GMT +Content-Type: text/plain +Content-Length: 42 + +abcdefghijklmnopqrstuvwxyz1234567890abcdef + diff --git a/cpan/HTTP-Tiny/t/cases/redirect-03.txt b/cpan/HTTP-Tiny/t/cases/redirect-03.txt new file mode 100644 index 0000000000..0a7df722a7 --- /dev/null +++ b/cpan/HTTP-Tiny/t/cases/redirect-03.txt @@ -0,0 +1,50 @@ +new_args + max_redirect: 1 +url + http://example.com/index.html +expected + <a href="http://example.com/index3.html">redirect</a> +---------- +GET /index.html HTTP/1.1 +Host: example.com +Connection: close +User-Agent: HTTP-Tiny/VERSION + +---------- +HTTP/1.1 302 Found +Date: Thu, 03 Feb 1994 00:00:00 GMT +Content-Type: text/html +Content-Length: 53 +Location: http://example.com/index2.html + +<a href="http://example.com/index2.html">redirect</a> + +---------- +GET /index2.html HTTP/1.1 +Host: example.com +Connection: close +User-Agent: HTTP-Tiny/VERSION + +---------- +HTTP/1.1 302 Found +Date: Thu, 03 Feb 1994 00:00:00 GMT +Content-Type: text/html +Content-Length: 53 +Location: http://example.com/index3.html + +<a href="http://example.com/index3.html">redirect</a> + +---------- +GET /index3.html HTTP/1.1 +Host: example.com +Connection: close +User-Agent: HTTP-Tiny/VERSION + +---------- +HTTP/1.1 200 OK +Date: Thu, 03 Feb 1994 00:00:00 GMT +Content-Type: text/plain +Content-Length: 42 + +abcdefghijklmnopqrstuvwxyz1234567890abcdef + diff --git a/cpan/HTTP-Tiny/t/cases/redirect-04.txt b/cpan/HTTP-Tiny/t/cases/redirect-04.txt new file mode 100644 index 0000000000..c07412bdc3 --- /dev/null +++ b/cpan/HTTP-Tiny/t/cases/redirect-04.txt @@ -0,0 +1,50 @@ +new_args + max_redirect: 2 +url + http://example.com/index.html +expected + abcdefghijklmnopqrstuvwxyz1234567890abcdef +---------- +GET /index.html HTTP/1.1 +Host: example.com +Connection: close +User-Agent: HTTP-Tiny/VERSION + +---------- +HTTP/1.1 302 Found +Date: Thu, 03 Feb 1994 00:00:00 GMT +Content-Type: text/html +Content-Length: 53 +Location: http://example.com/index2.html + +<a href="http://example.com/index2.html">redirect</a> + +---------- +GET /index2.html HTTP/1.1 +Host: example.com +Connection: close +User-Agent: HTTP-Tiny/VERSION + +---------- +HTTP/1.1 302 Found +Date: Thu, 03 Feb 1994 00:00:00 GMT +Content-Type: text/html +Content-Length: 53 +Location: http://example.com/index3.html + +<a href="http://example.com/index3.html">redirect</a> + +---------- +GET /index3.html HTTP/1.1 +Host: example.com +Connection: close +User-Agent: HTTP-Tiny/VERSION + +---------- +HTTP/1.1 200 OK +Date: Thu, 03 Feb 1994 00:00:00 GMT +Content-Type: text/plain +Content-Length: 42 + +abcdefghijklmnopqrstuvwxyz1234567890abcdef + diff --git a/cpan/HTTP-Tiny/t/cases/redirect-05.txt b/cpan/HTTP-Tiny/t/cases/redirect-05.txt new file mode 100644 index 0000000000..0691a80758 --- /dev/null +++ b/cpan/HTTP-Tiny/t/cases/redirect-05.txt @@ -0,0 +1,48 @@ +url + http://example.com/index.html +expected + abcdefghijklmnopqrstuvwxyz1234567890abcdef +---------- +GET /index.html HTTP/1.1 +Host: example.com +Connection: close +User-Agent: HTTP-Tiny/VERSION + +---------- +HTTP/1.1 302 Found +Date: Thu, 03 Feb 1994 00:00:00 GMT +Content-Type: text/html +Content-Length: 53 +Location: http://example.com/index2.html + +<a href="http://example.com/index2.html">redirect</a> + +---------- +GET /index2.html HTTP/1.1 +Host: example.com +Connection: close +User-Agent: HTTP-Tiny/VERSION + +---------- +HTTP/1.1 301 Found +Date: Thu, 03 Feb 1994 00:00:00 GMT +Content-Type: text/html +Content-Length: 53 +Location: /index3.html + +<a href="http://example.com/index3.html">redirect</a> + +---------- +GET /index3.html HTTP/1.1 +Host: example.com +Connection: close +User-Agent: HTTP-Tiny/VERSION + +---------- +HTTP/1.1 200 OK +Date: Thu, 03 Feb 1994 00:00:00 GMT +Content-Type: text/plain +Content-Length: 42 + +abcdefghijklmnopqrstuvwxyz1234567890abcdef + diff --git a/cpan/HTTP-Tiny/t/cases/redirect-06.txt b/cpan/HTTP-Tiny/t/cases/redirect-06.txt new file mode 100644 index 0000000000..b5a6a49762 --- /dev/null +++ b/cpan/HTTP-Tiny/t/cases/redirect-06.txt @@ -0,0 +1,33 @@ +url + http://example.com/index.html +expected + abcdefghijklmnopqrstuvwxyz1234567890abcdef +---------- +GET /index.html HTTP/1.1 +Host: example.com +Connection: close +User-Agent: HTTP-Tiny/VERSION + +---------- +HTTP/1.1 303 See Other +Date: Thu, 03 Feb 1994 00:00:00 GMT +Content-Type: text/html +Content-Length: 53 +Location: http://example.com/index2.html + +<a href="http://example.com/index2.html">redirect</a> + +---------- +GET /index2.html HTTP/1.1 +Host: example.com +Connection: close +User-Agent: HTTP-Tiny/VERSION + +---------- +HTTP/1.1 200 OK +Date: Thu, 03 Feb 1994 00:00:00 GMT +Content-Type: text/plain +Content-Length: 42 + +abcdefghijklmnopqrstuvwxyz1234567890abcdef + diff --git a/cpan/HTTP-Tiny/t/cases/redirect-07.txt b/cpan/HTTP-Tiny/t/cases/redirect-07.txt new file mode 100644 index 0000000000..3320c6ce3c --- /dev/null +++ b/cpan/HTTP-Tiny/t/cases/redirect-07.txt @@ -0,0 +1,33 @@ +url + http://example.com/index.html +expected + abcdefghijklmnopqrstuvwxyz1234567890abcdef +---------- +GET /index.html HTTP/1.1 +Host: example.com +Connection: close +User-Agent: HTTP-Tiny/VERSION + +---------- +HTTP/1.1 307 Temporary Redirect +Date: Thu, 03 Feb 1994 00:00:00 GMT +Content-Type: text/html +Content-Length: 53 +Location: http://example.com/index2.html + +<a href="http://example.com/index2.html">redirect</a> + +---------- +GET /index2.html HTTP/1.1 +Host: example.com +Connection: close +User-Agent: HTTP-Tiny/VERSION + +---------- +HTTP/1.1 200 OK +Date: Thu, 03 Feb 1994 00:00:00 GMT +Content-Type: text/plain +Content-Length: 42 + +abcdefghijklmnopqrstuvwxyz1234567890abcdef + diff --git a/cpan/HTTP-Tiny/t/cases/redirect-08.txt b/cpan/HTTP-Tiny/t/cases/redirect-08.txt new file mode 100644 index 0000000000..3f983b8402 --- /dev/null +++ b/cpan/HTTP-Tiny/t/cases/redirect-08.txt @@ -0,0 +1,19 @@ +url + http://example.com/index.html +expected + <a href="http://example.com/index2.html">redirect</a> +---------- +GET /index.html HTTP/1.1 +Host: example.com +Connection: close +User-Agent: HTTP-Tiny/VERSION + +---------- +HTTP/1.1 305 Use Proxy +Date: Thu, 03 Feb 1994 00:00:00 GMT +Content-Type: text/html +Content-Length: 53 +Location: http://example.com/index2.html + +<a href="http://example.com/index2.html">redirect</a> + diff --git a/cpan/HTTP-Tiny/t/cases/redirect-09.txt b/cpan/HTTP-Tiny/t/cases/redirect-09.txt new file mode 100644 index 0000000000..02a75aa63b --- /dev/null +++ b/cpan/HTTP-Tiny/t/cases/redirect-09.txt @@ -0,0 +1,35 @@ +url + http://example.com/index.html +method + POST +expected + abcdefghijklmnopqrstuvwxyz1234567890abcdef +---------- +POST /index.html HTTP/1.1 +Host: example.com +Connection: close +User-Agent: HTTP-Tiny/VERSION + +---------- +HTTP/1.1 303 See Other +Date: Thu, 03 Feb 1994 00:00:00 GMT +Content-Type: text/html +Content-Length: 53 +Location: http://example.com/index2.html + +<a href="http://example.com/index2.html">redirect</a> + +---------- +GET /index2.html HTTP/1.1 +Host: example.com +Connection: close +User-Agent: HTTP-Tiny/VERSION + +---------- +HTTP/1.1 200 OK +Date: Thu, 03 Feb 1994 00:00:00 GMT +Content-Type: text/plain +Content-Length: 42 + +abcdefghijklmnopqrstuvwxyz1234567890abcdef + diff --git a/lib/.gitignore b/lib/.gitignore index a266b89924..cb1ea9c4ad 100644 --- a/lib/.gitignore +++ b/lib/.gitignore @@ -210,6 +210,7 @@ /IPC/Semaphore.pm /IPC/SharedMem.pm /IPC/SysV.pm +/HTTP/Tiny.pm /JSON/PP.pm /JSON/PP/Boolean.pm /Getopt/Long.pm diff --git a/pod/perldelta.pod b/pod/perldelta.pod index 8f861d80c9..d3b0fd16c2 100644 --- a/pod/perldelta.pod +++ b/pod/perldelta.pod @@ -109,6 +109,14 @@ generation task. =item * +L<HTTP::Tiny> 0.008 has been added as a dual-life module. It is a very +small, simple HTTP/1.1 client designed for simple GET requests and file +mirroring. It has has been added to enable CPAN.pm and CPANPLUS to +"bootstrap" HTTP access to CPAN using pure Perl without relying on external +binaries like F<curl> or F<wget>. + +=item * + L<Module::Metadata> 1.000003 has been added as a dual-life module. It gathers package and POD information from Perl module files. It is a standalone module based on Module::Build::ModuleInfo for use by other module installation |