diff options
author | Lorry Tar Creator <lorry-tar-importer@lorry> | 2015-07-19 17:50:38 +0000 |
---|---|---|
committer | Lorry Tar Creator <lorry-tar-importer@lorry> | 2015-07-19 17:50:38 +0000 |
commit | d403562e3f7ac96df7cee2c1709ecd970b6c9761 (patch) | |
tree | 0c8ec1bc7a6e0bf408a0e183b52ef7de174cde9a /t | |
download | HTTP-Message-tarball-master.tar.gz |
HTTP-Message-6.10HEADHTTP-Message-6.10master
Diffstat (limited to 't')
-rw-r--r-- | t/common-req.t | 235 | ||||
-rw-r--r-- | t/headers-auth.t | 41 | ||||
-rw-r--r-- | t/headers-etag.t | 29 | ||||
-rw-r--r-- | t/headers-util.t | 45 | ||||
-rw-r--r-- | t/headers.t | 480 | ||||
-rw-r--r-- | t/http-config.t | 85 | ||||
-rw-r--r-- | t/message-charset.t | 124 | ||||
-rw-r--r-- | t/message-decode-xml.t | 33 | ||||
-rw-r--r-- | t/message-old.t | 97 | ||||
-rw-r--r-- | t/message-parts.t | 149 | ||||
-rw-r--r-- | t/message.t | 494 | ||||
-rw-r--r-- | t/request.t | 33 | ||||
-rw-r--r-- | t/request_type_with_data.t | 22 | ||||
-rw-r--r-- | t/response.t | 102 | ||||
-rw-r--r-- | t/status-old.t | 19 | ||||
-rw-r--r-- | t/status.t | 21 |
16 files changed, 2009 insertions, 0 deletions
diff --git a/t/common-req.t b/t/common-req.t new file mode 100644 index 0000000..589691f --- /dev/null +++ b/t/common-req.t @@ -0,0 +1,235 @@ +use strict; +use warnings; + +use Test::More; +plan tests => 59; + +use HTTP::Request::Common; + +my $r = GET 'http://www.sn.no/'; +note $r->as_string; + +is($r->method, "GET"); +is($r->uri, "http://www.sn.no/"); + +$r = HEAD "http://www.sn.no/", + If_Match => 'abc', + From => 'aas@sn.no'; +note $r->as_string; + +is($r->method, "HEAD"); +ok($r->uri->eq("http://www.sn.no")); + +is($r->header('If-Match'), "abc"); +is($r->header("from"), "aas\@sn.no"); + +$r = PUT "http://www.sn.no", + Content => 'foo'; +note $r->as_string, "\n"; + +is($r->method, "PUT"); +is($r->uri->host, "www.sn.no"); + +ok(!defined($r->header("Content"))); + +is(${$r->content_ref}, "foo"); +is($r->content, "foo"); +is($r->content_length, 3); + +$r = PUT "http://www.sn.no", + { foo => "bar" }; +is($r->content, "foo=bar"); + +#--- Test POST requests --- + +$r = POST "http://www.sn.no", [foo => 'bar;baz', + baz => [qw(a b c)], + foo => 'zoo=&', + "space " => " + ", + "nl" => "a\nb\r\nc\n", + ], + bar => 'foo'; +note $r->as_string, "\n"; + +is($r->method, "POST"); +is($r->content_type, "application/x-www-form-urlencoded"); +is($r->content_length, 83); +is($r->header("bar"), "foo"); +is($r->content, "foo=bar%3Bbaz&baz=a&baz=b&baz=c&foo=zoo%3D%26&space+=+%2B+&nl=a%0D%0Ab%0D%0Ac%0D%0A"); + +$r = POST "http://example.com"; +is($r->content_length, 0); +is($r->content, ""); + +$r = POST "http://example.com", []; +is($r->content_length, 0); +is($r->content, ""); + +$r = POST "mailto:gisle\@aas.no", + Subject => "Heisan", + Content_Type => "text/plain", + Content => "Howdy\n"; +#note $r->as_string; + +is($r->method, "POST"); +is($r->header("Subject"), "Heisan"); +is($r->content, "Howdy\n"); +is($r->content_type, "text/plain"); + +{ + my @warnings; + local $SIG{__WARN__} = sub { push @warnings, @_ }; + $r = POST 'http://unf.ug/', []; + is( "@warnings", '', 'empty POST' ); +} + +# +# POST for File upload +# +my $file = "test-$$"; +open(FILE, ">$file") or die "Can't create $file: $!"; +print FILE "foo\nbar\nbaz\n"; +close(FILE); + +$r = POST 'http://www.perl.org/survey.cgi', + Content_Type => 'form-data', + Content => [ name => 'Gisle Aas', + email => 'gisle@aas.no', + gender => 'm', + born => '1964', + file => [$file], + ]; +#note $r->as_string; + +unlink($file) or warn "Can't unlink $file: $!"; + +is($r->method, "POST"); +is($r->uri->path, "/survey.cgi"); +is($r->content_type, "multipart/form-data"); +ok($r->header('Content_type') =~ /boundary="?([^"]+)"?/); +my $boundary = $1; + +my $c = $r->content; +$c =~ s/\r//g; +my @c = split(/--\Q$boundary/, $c); +note "$c[5]\n"; + +is(@c, 7); +like($c[6], qr/^--\n/); # 5 parts + header & trailer + +ok($c[2] =~ /^Content-Disposition:\s*form-data;\s*name="email"/m); +ok($c[2] =~ /^gisle\@aas.no$/m); + +ok($c[5] =~ /^Content-Disposition:\s*form-data;\s*name="file";\s*filename="$file"/m); +ok($c[5] =~ /^Content-Type:\s*text\/plain$/m); +ok($c[5] =~ /^foo\nbar\nbaz/m); + +$r = POST 'http://www.perl.org/survey.cgi', + [ file => [ undef, "xxy\"", Content_type => "text/html", Content => "<h1>Hello, world!</h1>" ]], + Content_type => 'multipart/form-data'; +#note $r->as_string; + +ok($r->content =~ /^--\S+\015\012Content-Disposition:\s*form-data;\s*name="file";\s*filename="xxy\\"/m); +ok($r->content =~ /^Content-Type: text\/html/m); +ok($r->content =~ /^<h1>Hello, world/m); + +$r = POST 'http://www.perl.org/survey.cgi', + Content_type => 'multipart/form-data', + Content => [ file => [ undef, undef, Content => "foo"]]; +#note $r->as_string; + +unlike($r->content, qr/filename=/); + + +# The POST routine can now also take a hash reference. +my %hash = (foo => 42, bar => 24); +$r = POST 'http://www.perl.org/survey.cgi', \%hash; +#note $r->as_string, "\n"; +like($r->content, qr/foo=42/); +like($r->content, qr/bar=24/); +is($r->content_type, "application/x-www-form-urlencoded"); +is($r->content_length, 13); + + +# +# POST for File upload +# +use HTTP::Request::Common qw($DYNAMIC_FILE_UPLOAD); + +$file = "test-$$"; +open(FILE, ">$file") or die "Can't create $file: $!"; +for (1..1000) { + print FILE "a" .. "z"; +} +close(FILE); + +$DYNAMIC_FILE_UPLOAD++; +$r = POST 'http://www.perl.org/survey.cgi', + Content_Type => 'form-data', + Content => [ name => 'Gisle Aas', + email => 'gisle@aas.no', + gender => 'm', + born => '1964', + file => [$file], + ]; +#note $r->as_string, "\n"; + +is($r->method, "POST"); +is($r->uri->path, "/survey.cgi"); +is($r->content_type, "multipart/form-data"); +ok($r->header('Content_type') =~ qr/boundary="?([^"]+)"?/); +$boundary = $1; +is(ref($r->content), "CODE"); + +cmp_ok(length($boundary), '>', 10); + +my $code = $r->content; +my $chunk; +my @chunks; +while (defined($chunk = &$code) && length $chunk) { + push(@chunks, $chunk); +} + +unlink($file) or warn "Can't unlink $file: $!"; + +$_ = join("", @chunks); + +#note int(@chunks), " chunks, total size is ", length($_), " bytes\n"; + +# should be close to expected size and number of chunks +cmp_ok(abs(@chunks - 15), '<', 3); +cmp_ok(abs(length($_) - 26589), '<', 20); + +$r = POST 'http://www.example.com'; +is($r->as_string, <<EOT); +POST http://www.example.com +Content-Length: 0 +Content-Type: application/x-www-form-urlencoded + +EOT + +$r = POST 'http://www.example.com', Content_Type => 'form-data', Content => []; +is($r->as_string, <<EOT); +POST http://www.example.com +Content-Length: 0 +Content-Type: multipart/form-data; boundary=none + +EOT + +$r = POST 'http://www.example.com', Content_Type => 'form-data'; +#note $r->as_string; +is($r->as_string, <<EOT); +POST http://www.example.com +Content-Length: 0 +Content-Type: multipart/form-data + +EOT + +$r = HTTP::Request::Common::DELETE 'http://www.example.com'; +is($r->method, "DELETE"); + +$r = HTTP::Request::Common::PUT 'http://www.example.com', + 'Content-Type' => 'application/octet-steam', + 'Content' => 'foobarbaz', + 'Content-Length' => 12; # a slight lie +is($r->header('Content-Length'), 9); diff --git a/t/headers-auth.t b/t/headers-auth.t new file mode 100644 index 0000000..330d33c --- /dev/null +++ b/t/headers-auth.t @@ -0,0 +1,41 @@ +use strict; +use warnings; + +use Test::More; + +plan tests => 6; + +use HTTP::Response; +use HTTP::Headers::Auth; + +my $res = HTTP::Response->new(401); +$res->push_header(WWW_Authenticate => qq(Foo realm="WallyWorld", foo=bar, Bar realm="WallyWorld2")); +$res->push_header(WWW_Authenticate => qq(Basic Realm="WallyWorld", foo=bar, bar=baz)); + +note $res->as_string; + +my %auth = $res->www_authenticate; + +is(keys(%auth), 3); + +is($auth{basic}{realm}, "WallyWorld"); +is($auth{bar}{realm}, "WallyWorld2"); + +$a = $res->www_authenticate; +is($a, 'Foo realm="WallyWorld", foo=bar, Bar realm="WallyWorld2", Basic Realm="WallyWorld", foo=bar, bar=baz'); + +$res->www_authenticate("Basic realm=foo1"); +note $res->as_string; + +$res->www_authenticate(Basic => {realm => "foo2"}); +print $res->as_string; + +$res->www_authenticate(Basic => [realm => "foo3", foo=>33], + Digest => {nonce=>"bar", foo=>'foo'}); +note $res->as_string; + +my $string = $res->as_string; + +like($string, qr/WWW-Authenticate: Basic realm="foo3", foo=33/); +like($string, qr/WWW-Authenticate: Digest (nonce=bar, foo=foo|foo=foo, nonce=bar)/); + diff --git a/t/headers-etag.t b/t/headers-etag.t new file mode 100644 index 0000000..5713f3d --- /dev/null +++ b/t/headers-etag.t @@ -0,0 +1,29 @@ +use strict; +use warnings; + +use Test::More; + +plan tests => 4; + +require HTTP::Headers::ETag; + +my $h = HTTP::Headers->new; + +$h->etag("tag1"); +is($h->etag, qq("tag1")); + +$h->etag("w/tag2"); +is($h->etag, qq(W/"tag2")); + +$h->if_match(qq(W/"foo", bar, baz), "bar"); +$h->if_none_match(333); + +$h->if_range("tag3"); +is($h->if_range, qq("tag3")); + +my $t = time; +$h->if_range($t); +is($h->if_range, $t); + +note $h->as_string; + diff --git a/t/headers-util.t b/t/headers-util.t new file mode 100644 index 0000000..ee7717a --- /dev/null +++ b/t/headers-util.t @@ -0,0 +1,45 @@ +use strict; +use warnings; + +use Test::More; + +use HTTP::Headers::Util qw(split_header_words join_header_words); + +my @s_tests = ( + + ["foo" => "foo"], + ["foo=bar" => "foo=bar"], + [" foo " => "foo"], + ["foo=" => 'foo=""'], + ["foo=bar bar=baz" => "foo=bar; bar=baz"], + ["foo=bar;bar=baz" => "foo=bar; bar=baz"], + ['foo bar baz' => "foo; bar; baz"], + ['foo="\"" bar="\\\\"' => 'foo="\""; bar="\\\\"'], + ['foo,,,bar' => 'foo, bar'], + ['foo=bar,bar=baz' => 'foo=bar, bar=baz'], + + ['TEXT/HTML; CHARSET=ISO-8859-1' => + 'text/html; charset=ISO-8859-1'], + + ['foo="bar"; port="80,81"; discard, bar=baz' => + 'foo=bar; port="80,81"; discard, bar=baz'], + + ['Basic realm="\"foo\\\\bar\""' => + 'basic; realm="\"foo\\\\bar\""'], +); + +plan tests => @s_tests + 2; + +for (@s_tests) { + my($arg, $expect) = @$_; + my @arg = ref($arg) ? @$arg : $arg; + + my $res = join_header_words(split_header_words(@arg)); + is($res, $expect); +} + + +note "# Extra tests\n"; +# some extra tests +is(join_header_words("foo" => undef, "bar" => "baz"), "foo; bar=baz"); +is(join_header_words(), ""); diff --git a/t/headers.t b/t/headers.t new file mode 100644 index 0000000..70785cb --- /dev/null +++ b/t/headers.t @@ -0,0 +1,480 @@ +use strict; +use warnings; + +use Test::More; + +plan tests => 168; + +my($h, $h2); +sub j { join("|", @_) } + + +require HTTP::Headers; +$h = HTTP::Headers->new; +ok($h); +is(ref($h), "HTTP::Headers"); +is($h->as_string, ""); + +$h = HTTP::Headers->new(foo => "bar", foo => "baaaaz", Foo => "baz"); +is($h->as_string, "Foo: bar\nFoo: baaaaz\nFoo: baz\n"); + +$h = HTTP::Headers->new(foo => ["bar", "baz"]); +is($h->as_string, "Foo: bar\nFoo: baz\n"); + +$h = HTTP::Headers->new(foo => 1, bar => 2, foo_bar => 3); +is($h->as_string, "Bar: 2\nFoo: 1\nFoo-Bar: 3\n"); +is($h->as_string(";"), "Bar: 2;Foo: 1;Foo-Bar: 3;"); + +is($h->header("Foo"), 1); +is($h->header("FOO"), 1); +is(j($h->header("foo")), 1); +is($h->header("foo-bar"), 3); +is($h->header("foo_bar"), 3); +is($h->header("Not-There"), undef); +is(j($h->header("Not-There")), ""); +is(eval { $h->header }, undef); +ok($@); + +is($h->header("Foo", 11), 1); +is($h->header("Foo", [1, 1]), 11); +is($h->header("Foo"), "1, 1"); +is(j($h->header("Foo")), "1|1"); +is($h->header(foo => 11, Foo => 12, bar => 22), 2); +is($h->header("Foo"), "11, 12"); +is($h->header("Bar"), 22); +is($h->header("Bar", undef), 22); +is(j($h->header("bar", 22)), ""); + +$h->push_header(Bar => 22); +is($h->header("Bar"), "22, 22"); +$h->push_header(Bar => [23 .. 25]); +is($h->header("Bar"), "22, 22, 23, 24, 25"); +is(j($h->header("Bar")), "22|22|23|24|25"); + +$h->clear; +$h->header(Foo => 1); +is($h->as_string, "Foo: 1\n"); +$h->init_header(Foo => 2); +$h->init_header(Bar => 2); +is($h->as_string, "Bar: 2\nFoo: 1\n"); +$h->init_header(Foo => [2, 3]); +$h->init_header(Baz => [2, 3]); +is($h->as_string, "Bar: 2\nBaz: 2\nBaz: 3\nFoo: 1\n"); + +eval { $h->init_header(A => 1, B => 2, C => 3) }; +ok($@); +is($h->as_string, "Bar: 2\nBaz: 2\nBaz: 3\nFoo: 1\n"); + +is($h->clone->remove_header("Foo"), 1); +is($h->clone->remove_header("Bar"), 1); +is($h->clone->remove_header("Baz"), 2); +is($h->clone->remove_header(qw(Foo Bar Baz Not-There)), 4); +is($h->clone->remove_header("Not-There"), 0); +is(j($h->clone->remove_header("Foo")), 1); +is(j($h->clone->remove_header("Bar")), 2); +is(j($h->clone->remove_header("Baz")), "2|3"); +is(j($h->clone->remove_header(qw(Foo Bar Baz Not-There))), "1|2|2|3"); +is(j($h->clone->remove_header("Not-There")), ""); + +$h = HTTP::Headers->new( + allow => "GET", + content => "none", + content_type => "text/html", + content_md5 => "dummy", + content_encoding => "gzip", + content_foo => "bar", + last_modified => "yesterday", + expires => "tomorrow", + etag => "abc", + date => "today", + user_agent => "libwww-perl", + zoo => "foo", + ); +is($h->as_string, <<EOT); +Date: today +User-Agent: libwww-perl +ETag: abc +Allow: GET +Content-Encoding: gzip +Content-MD5: dummy +Content-Type: text/html +Expires: tomorrow +Last-Modified: yesterday +Content: none +Content-Foo: bar +Zoo: foo +EOT + +$h2 = $h->clone; +is($h->as_string, $h2->as_string); + +is($h->remove_content_headers->as_string, <<EOT); +Allow: GET +Content-Encoding: gzip +Content-MD5: dummy +Content-Type: text/html +Expires: tomorrow +Last-Modified: yesterday +Content-Foo: bar +EOT + +is($h->as_string, <<EOT); +Date: today +User-Agent: libwww-perl +ETag: abc +Content: none +Zoo: foo +EOT + +# separate code path for the void context case, so test it as well +$h2->remove_content_headers; +is($h->as_string, $h2->as_string); + +$h->clear; +is($h->as_string, ""); +undef($h2); + +$h = HTTP::Headers->new; +is($h->header_field_names, 0); +is(j($h->header_field_names), ""); + +$h = HTTP::Headers->new( etag => 1, foo => [2,3], + content_type => "text/plain"); +is($h->header_field_names, 3); +is(j($h->header_field_names), "ETag|Content-Type|Foo"); + +{ + my @tmp; + $h->scan(sub { push(@tmp, @_) }); + is(j(@tmp), "ETag|1|Content-Type|text/plain|Foo|2|Foo|3"); + + @tmp = (); + eval { $h->scan(sub { push(@tmp, @_); die if $_[0] eq "Content-Type" }) }; + ok($@); + is(j(@tmp), "ETag|1|Content-Type|text/plain"); + + @tmp = (); + $h->scan(sub { push(@tmp, @_) }); + is(j(@tmp), "ETag|1|Content-Type|text/plain|Foo|2|Foo|3"); +} + +# CONVENIENCE METHODS + +$h = HTTP::Headers->new; +is($h->date, undef); +is($h->date(time), undef); +is(j($h->header_field_names), "Date"); +like($h->header("Date"), qr/^[A-Z][a-z][a-z], \d\d .* GMT$/); +{ + my $off = time - $h->date; + ok($off == 0 || $off == 1); +} + +if ($] < 5.006) { + Test::skip("Can't call variable method", 1) for 1..13; +} +else { +# other date fields +for my $field (qw(expires if_modified_since if_unmodified_since + last_modified)) +{ + eval <<'EOT'; die $@ if $@; + is($h->$field, undef); + is($h->$field(time), undef); + like((time - $h->$field), qr/^[01]$/); +EOT +} +is(j($h->header_field_names), "Date|If-Modified-Since|If-Unmodified-Since|Expires|Last-Modified"); +} + +$h->clear; +is($h->content_type, ""); +is($h->content_type("text/html"), ""); +is($h->content_type, "text/html"); +is($h->content_type(" TEXT / HTML ") , "text/html"); +is($h->content_type, "text/html"); +is(j($h->content_type), "text/html"); +is($h->content_type("text/html;\n charSet = \"ISO-8859-1\"; Foo=1 "), "text/html"); +is($h->content_type, "text/html"); +is(j($h->content_type), "text/html|charSet = \"ISO-8859-1\"; Foo=1 "); +is($h->header("content_type"), "text/html;\n charSet = \"ISO-8859-1\"; Foo=1 "); +ok($h->content_is_html); +ok(!$h->content_is_xhtml); +ok(!$h->content_is_xml); +$h->content_type("application/xhtml+xml"); +ok($h->content_is_html); +ok($h->content_is_xhtml); +ok($h->content_is_xml); +is($h->content_type("text/html;\n charSet = \"ISO-8859-1\"; Foo=1 "), "application/xhtml+xml"); + +is($h->content_encoding, undef); +is($h->content_encoding("gzip"), undef); +is($h->content_encoding, "gzip"); +is(j($h->header_field_names), "Content-Encoding|Content-Type"); + +is($h->content_language, undef); +is($h->content_language("no"), undef); +is($h->content_language, "no"); + +is($h->title, undef); +is($h->title("This is a test"), undef); +is($h->title, "This is a test"); + +is($h->user_agent, undef); +is($h->user_agent("Mozilla/1.2"), undef); +is($h->user_agent, "Mozilla/1.2"); + +is($h->server, undef); +is($h->server("Apache/2.1"), undef); +is($h->server, "Apache/2.1"); + +is($h->from("Gisle\@ActiveState.com"), undef); +ok($h->header("from", "Gisle\@ActiveState.com")); + +is($h->referer("http://www.example.com"), undef); +is($h->referer, "http://www.example.com"); +is($h->referrer, "http://www.example.com"); +is($h->referer("http://www.example.com/#bar"), "http://www.example.com"); +is($h->referer, "http://www.example.com/"); +{ + require URI; + my $u = URI->new("http://www.example.com#bar"); + $h->referer($u); + is($u->as_string, "http://www.example.com#bar"); + is($h->referer->fragment, undef); + is($h->referrer->as_string, "http://www.example.com"); +} + +is($h->as_string, <<EOT); +From: Gisle\@ActiveState.com +Referer: http://www.example.com +User-Agent: Mozilla/1.2 +Server: Apache/2.1 +Content-Encoding: gzip +Content-Language: no +Content-Type: text/html; + charSet = "ISO-8859-1"; Foo=1 +Title: This is a test +EOT + +$h->clear; +is($h->www_authenticate("foo"), undef); +is($h->www_authenticate("bar"), "foo"); +is($h->www_authenticate, "bar"); +is($h->proxy_authenticate("foo"), undef); +is($h->proxy_authenticate("bar"), "foo"); +is($h->proxy_authenticate, "bar"); + +is($h->authorization_basic, undef); +is($h->authorization_basic("u"), undef); +is($h->authorization_basic("u", "p"), "u:"); +is($h->authorization_basic, "u:p"); +is(j($h->authorization_basic), "u|p"); +is($h->authorization, "Basic dTpw"); + +is(eval { $h->authorization_basic("u2:p") }, undef); +ok($@); +is(j($h->authorization_basic), "u|p"); + +is($h->proxy_authorization_basic("u2", "p2"), undef); +is(j($h->proxy_authorization_basic), "u2|p2"); +is($h->proxy_authorization, "Basic dTI6cDI="); + +is($h->as_string, <<EOT); +Authorization: Basic dTpw +Proxy-Authorization: Basic dTI6cDI= +Proxy-Authenticate: bar +WWW-Authenticate: bar +EOT + +# Try some bad field names +my $file = __FILE__; +my $line; +$h = HTTP::Headers->new; +eval { + $line = __LINE__; $h->header('foo:', 1); +}; +like($@, qr/^Illegal field name 'foo:' at \Q$file\E line $line/); +eval { + $line = __LINE__; $h->header('', 2); +}; +like($@, qr/^Illegal field name '' at \Q$file\E line $line/); + + + +#---- old tests below ----- + +$h = new HTTP::Headers + mime_version => "1.0", + content_type => "text/html"; +$h->header(URI => "http://www.oslonett.no/"); + +is($h->header("MIME-Version"), "1.0"); +is($h->header('Uri'), "http://www.oslonett.no/"); + +$h->header("MY-header" => "foo", + "Date" => "somedate", + "Accept" => ["text/plain", "image/*"], + ); +$h->push_header("accept" => "audio/basic"); + +is($h->header("date"), "somedate"); + +my @accept = $h->header("accept"); +is(@accept, 3); + +$h->remove_header("uri", "date"); + +my $str = $h->as_string; +my $lines = ($str =~ tr/\n/\n/); +is($lines, 6); + +$h2 = $h->clone; + +$h->header("accept", "*/*"); +$h->remove_header("my-header"); + +@accept = $h2->header("accept"); +is(@accept, 3); + +@accept = $h->header("accept"); +is(@accept, 1); + +# Check order of headers, but first remove this one +$h2->remove_header('mime_version'); + +# and add this general header +$h2->header(Connection => 'close'); + +my @x = (); +$h2->scan(sub {push(@x, shift);}); +is(join(";", @x), "Connection;Accept;Accept;Accept;Content-Type;MY-Header"); + +# Check headers with embedded newlines: +$h = HTTP::Headers->new( + a => "foo\n\n", + b => "foo\nbar", + c => "foo\n\nbar\n\n", + d => "foo\n\tbar", + e => "foo\n bar ", + f => "foo\n bar\n baz\nbaz", + ); +is($h->as_string("<<\n"), <<EOT); +A: foo<< +B: foo<< + bar<< +C: foo<< + bar<< +D: foo<< +\tbar<< +E: foo<< + bar<< +F: foo<< + bar<< + baz<< + baz<< +EOT + +# Check for attempt to send a body +$h = HTTP::Headers->new( + a => "foo\r\n\r\nevil body" , + b => "foo\015\012\015\012evil body" , + c => "foo\x0d\x0a\x0d\x0aevil body" , +); +is ( + $h->as_string(), + "A: foo\r\n evil body\n". + "B: foo\015\012 evil body\n" . + "C: foo\x0d\x0a evil body\n" , + "embedded CRLF are stripped out"); + +# Check with FALSE $HTML::Headers::TRANSLATE_UNDERSCORE +{ + local($HTTP::Headers::TRANSLATE_UNDERSCORE); + $HTTP::Headers::TRANSLATE_UNDERSCORE = undef; # avoid -w warning + + $h = HTTP::Headers->new; + $h->header(abc_abc => "foo"); + $h->header("abc-abc" => "bar"); + + is($h->header("ABC_ABC"), "foo"); + is($h->header("ABC-ABC"),"bar"); + ok($h->remove_header("Abc_Abc")); + ok(!defined($h->header("abc_abc"))); + is($h->header("ABC-ABC"), "bar"); +} + +# Check if objects as header values works +require URI; +$h->header(URI => URI->new("http://www.perl.org")); + +is($h->header("URI")->scheme, "http"); + +$h->clear; +is($h->as_string, ""); + +$h->content_type("text/plain"); +$h->header(content_md5 => "dummy"); +$h->header("Content-Foo" => "foo"); +$h->header(Location => "http:", xyzzy => "plugh!"); + +is($h->as_string, <<EOT); +Location: http: +Content-MD5: dummy +Content-Type: text/plain +Content-Foo: foo +Xyzzy: plugh! +EOT + +my $c = $h->remove_content_headers; +is($h->as_string, <<EOT); +Location: http: +Xyzzy: plugh! +EOT + +is($c->as_string, <<EOT); +Content-MD5: dummy +Content-Type: text/plain +Content-Foo: foo +EOT + +$h = HTTP::Headers->new; +$h->content_type("text/plain"); +$h->header(":foo_bar", 1); +$h->push_header(":content_type", "text/html"); +is(j($h->header_field_names), "Content-Type|:content_type|:foo_bar"); +is($h->header('Content-Type'), "text/plain"); +is($h->header(':Content_Type'), undef); +is($h->header(':content_type'), "text/html"); +is($h->as_string, <<EOT); +Content-Type: text/plain +content_type: text/html +foo_bar: 1 +EOT + +# [RT#30579] IE6 appens "; length = NNNN" on If-Modified-Since (can we handle it) +$h = HTTP::Headers->new( + if_modified_since => "Sat, 29 Oct 1994 19:43:31 GMT; length=34343" +); +is(gmtime($h->if_modified_since), "Sat Oct 29 19:43:31 1994"); + +$h = HTTP::Headers->new(); +$h->content_type('text/plain'); +$h->content_length(4); +$h->push_header('x-foo' => 'bar'); +$h->push_header('x-foo' => 'baz'); +is(0+$h->flatten, 8); +is_deeply( + [ $h->flatten ], + [ + 'Content-Length', + 4, + 'Content-Type', + 'text/plain', + 'X-Foo', + 'bar', + 'X-Foo', + 'baz', + ], +); + diff --git a/t/http-config.t b/t/http-config.t new file mode 100644 index 0000000..1cd42d8 --- /dev/null +++ b/t/http-config.t @@ -0,0 +1,85 @@ +use strict; +use warnings; + +use Test::More; +plan tests => 16; + +use HTTP::Config; + +sub j { join("|", @_) } + +my $conf = HTTP::Config->new; +ok($conf->empty); +$conf->add_item(42); +ok(!$conf->empty); +is(j($conf->matching_items("http://www.example.com/foo")), 42); +is(j($conf->remove_items), 42); +is($conf->matching_items("http://www.example.com/foo"), 0); + +$conf = HTTP::Config->new; + +$conf->add_item("always"); +$conf->add_item("GET", m_method => ["GET", "HEAD"]); +$conf->add_item("POST", m_method => "POST"); +$conf->add_item(".com", m_domain => ".com"); +$conf->add_item("secure", m_secure => 1); +$conf->add_item("not secure", m_secure => 0); +$conf->add_item("slash", m_host_port => "www.example.com:80", m_path_prefix => "/"); +$conf->add_item("u:p", m_host_port => "www.example.com:80", m_path_prefix => "/foo"); +$conf->add_item("success", m_code => "2xx"); + +use HTTP::Request; +my $request = HTTP::Request->new(HEAD => "http://www.example.com/foo/bar"); +$request->header("User-Agent" => "Moz/1.0"); + +is(j($conf->matching_items($request)), "u:p|slash|.com|GET|not secure|always"); + +$request->method("HEAD"); +$request->uri->scheme("https"); + +is(j($conf->matching_items($request)), ".com|GET|secure|always"); + +is(j($conf->matching_items("http://activestate.com")), ".com|not secure|always"); + +use HTTP::Response; +my $response = HTTP::Response->new(200 => "OK"); +$response->content_type("text/plain"); +$response->content("Hello, world!\n"); +$response->request($request); + +is(j($conf->matching_items($response)), ".com|success|GET|secure|always"); + +$conf->remove_items(m_secure => 1); +$conf->remove_items(m_domain => ".com"); +is(j($conf->matching_items($response)), "success|GET|always"); + +$conf->remove_items; # start fresh +is(j($conf->matching_items($response)), ""); + +$conf->add_item("any", "m_media_type" => "*/*"); +$conf->add_item("text", m_media_type => "text/*"); +$conf->add_item("html", m_media_type => "html"); +$conf->add_item("HTML", m_media_type => "text/html"); +$conf->add_item("xhtml", m_media_type => "xhtml"); + +is(j($conf->matching_items($response)), "text|any"); + +$response->content_type("application/xhtml+xml"); +is(j($conf->matching_items($response)), "xhtml|html|any"); + +$response->content_type("text/html"); +is(j($conf->matching_items($response)), "HTML|html|text|any"); + + +{ + my @warnings; + local $SIG{__WARN__} = sub { push @warnings, grep { length } @_ }; + + my $conf = HTTP::Config->new; + $conf->add(owner => undef, callback => sub { 'bleah' }); + $conf->remove(owner => undef); + + ok(($conf->empty), 'found and removed the config entry'); + is(scalar(@warnings), 0, 'no warnings') + or diag('got warnings: ', explain(\@warnings)); +} diff --git a/t/message-charset.t b/t/message-charset.t new file mode 100644 index 0000000..f6ad9f4 --- /dev/null +++ b/t/message-charset.t @@ -0,0 +1,124 @@ +use strict; +use warnings; + +use Test::More; +plan tests => 43; + +use HTTP::Response; +my $r = HTTP::Response->new(200, "OK"); +is($r->content_charset, undef); +is($r->content_type_charset, undef); + +$r->content_type("text/plain"); +is($r->content_charset, undef); + +$r->content("abc"); +is($r->content_charset, "US-ASCII"); + +$r->content("f\xE5rep\xF8lse\n"); +is($r->content_charset, "ISO-8859-1"); + +$r->content("f\xC3\xA5rep\xC3\xB8lse\n"); +is($r->content_charset, "UTF-8"); + +$r->content_type("text/html"); +$r->content(<<'EOT'); +<meta charset="UTF-8"> +EOT +is($r->content_charset, "UTF-8"); + +$r->content(<<'EOT'); +<body> +<META CharSet="Utf-16-LE"> +<meta charset="ISO-8859-1"> +EOT +is($r->content_charset, "UTF-8"); + +$r->content(<<'EOT'); +<!-- <meta charset="UTF-8"> +EOT +is($r->content_charset, "US-ASCII"); + +$r->content(<<'EOT'); +<meta content="text/plain; charset=UTF-8"> +EOT +is($r->content_charset, "UTF-8"); + +$r->content_type('text/plain; charset="iso-8859-1"'); +is($r->content_charset, "ISO-8859-1"); +is($r->content_type_charset, "ISO-8859-1"); + +$r->content_type("application/xml"); +$r->content("<foo>..</foo>"); +is($r->content_charset, "UTF-8"); + +require Encode; +for my $enc ("UTF-16BE", "UTF-16LE", "UTF-32BE", "UTF-32LE") { + $r->content(Encode::encode($enc, "<foo>..</foo>")); + is($r->content_charset, $enc); +} + +$r->content(<<'EOT'); +<?xml version="1.0" encoding="utf8" ?> +EOT +is($r->content_charset, "utf8"); + +$r->content(<<'EOT'); +<?xml version="1.0" encoding=" "?> +EOT +is($r->content_charset, "UTF-8"); + +$r->content(<<'EOT'); +<?xml version="1.0" encoding=" ISO-8859-1 "?> +EOT +is($r->content_charset, "ISO-8859-1"); + +$r->content(<<'EOT'); +<?xml version="1.0" +encoding="US-ASCII" ?> +EOT +is($r->content_charset, "US-ASCII"); + +$r->content_type("application/json"); +for my $enc ("UTF-8", "UTF-16BE", "UTF-16LE", "UTF-32BE", "UTF-32LE") { + $r->content(Encode::encode($enc, "{}")); + is($r->content_charset, $enc); +} + +{ + sub TIESCALAR{bless[]} + tie $_, ""; + my $fail = 0; + sub STORE{ ++$fail } + sub FETCH{} + $r->content_charset; + is($fail, 0, 'content_charset leaves $_ alone'); +} + +$r->remove_content_headers; +$r->content_type("text/plain; charset=UTF-8"); +$r->content("abc"); +is($r->decoded_content, "abc"); + +$r->content("\xc3\xa5"); +is($r->decoded_content, chr(0xE5)); +is($r->decoded_content(charset => "none"), "\xC3\xA5"); +is($r->decoded_content(alt_charset => "UTF-8"), chr(0xE5)); +is($r->decoded_content(alt_charset => "none"), chr(0xE5)); + +$r->content_type("text/plain; charset=UTF"); +is($r->decoded_content, undef); +is($r->decoded_content(charset => "UTF-8"), chr(0xE5)); +is($r->decoded_content(charset => "none"), "\xC3\xA5"); +is($r->decoded_content(alt_charset => "UTF-8"), chr(0xE5)); +is($r->decoded_content(alt_charset => "none"), "\xC3\xA5"); + +# char semantics for latin-1? +is($r->decoded_content(charset => "iso-8859-1"), "\xC3\xA5"); +is(lc($r->decoded_content(charset => "iso-8859-1")), "\xE3\xA5"); + +$r->content_type("text/plain"); +is($r->decoded_content, chr(0xE5)); +is($r->decoded_content(charset => "none"), "\xC3\xA5"); +is($r->decoded_content(default_charset => "ISO-8859-1"), "\xC3\xA5"); +is($r->decoded_content(default_charset => "latin1"), "\xC3\xA5"); diff --git a/t/message-decode-xml.t b/t/message-decode-xml.t new file mode 100644 index 0000000..0bf7626 --- /dev/null +++ b/t/message-decode-xml.t @@ -0,0 +1,33 @@ +# https://rt.cpan.org/Public/Bug/Display.html?id=52572 + +use strict; +use warnings; + +use Test::More; +plan tests => 2; + +use Encode qw( encode ); +use HTTP::Headers qw( ); +use HTTP::Response qw( ); +use PerlIO::encoding qw( ); + +{ + my $builder = Test::More->builder; + local $PerlIO::encoding::fallback = Encode::PERLQQ(); + binmode $builder->output, ":encoding(US-ASCII)"; + binmode $builder->failure_output, ":encoding(US-ASCII)"; + binmode $builder->todo_output, ":encoding(US-ASCII)"; +} + +for my $enc (qw( UTF-8 UTF-16le )) { + my $file = encode($enc, + ($enc =~ /^UTF-/ ? "\x{FEFF}" : "") . + qq{<?xml version="1.0" encoding="$enc"?>\n} . + qq{<root>\x{C9}ric</root>\n} + ); + + my $headers = HTTP::Headers->new(Content_Type => "application/xml"); + my $response = HTTP::Response->new(200, "OK", $headers, $file); + + is($response->decoded_content, qq(<?xml version="1.0"?>\n<root>\x{c9}ric</root>\n), $enc); +} diff --git a/t/message-old.t b/t/message-old.t new file mode 100644 index 0000000..224b50c --- /dev/null +++ b/t/message-old.t @@ -0,0 +1,97 @@ +# This is the old message.t test. It is not maintained any more, +# but kept around in case it happens to catch any mistakes. Please +# add new tests to message.t instead. + +use strict; +use warnings; + +use Test::More; + +plan tests => 20; + +require HTTP::Request; +require HTTP::Response; + +require Time::Local if $^O eq "MacOS"; +my $offset = ($^O eq "MacOS") ? Time::Local::timegm(0,0,0,1,0,70) : 0; + +my $req = HTTP::Request->new(GET => "http://www.sn.no/"); +$req->header( + "if-modified-since" => "Thu, 03 Feb 1994 00:00:00 GMT", + "mime-version" => "1.0"); + +ok($req->as_string =~ /^GET/m); +is($req->header("MIME-Version"), "1.0"); +is($req->if_modified_since, ((760233600 + $offset) || 0)); + +$req->content("gisle"); +$req->add_content(" aas"); +$req->add_content(\ " old interface is depreciated"); +${$req->content_ref} =~ s/\s+is\s+depreciated//; + +is($req->content, "gisle aas old interface"); + +my $time = time; +$req->date($time); +my $timestr = gmtime($time); +my($month) = ($timestr =~ /^\S+\s+(\S+)/); # extract month; +#print "These should represent the same time:\n\t", $req->header('Date'), "\n\t$timestr\n"; +like($req->header('Date'), qr/\Q$month/); + +$req->authorization_basic("gisle", "passwd"); +is($req->header("Authorization"), "Basic Z2lzbGU6cGFzc3dk"); + +my($user, $pass) = $req->authorization_basic; +is($user, "gisle"); +is($pass, "passwd"); + +# Check the response +my $res = HTTP::Response->new(200, "This message"); +ok($res->is_success); + +my $html = $res->error_as_HTML; +ok($html =~ /<head>/i && $html =~ /This message/); + +$res->content_type("text/html;version=3.0"); +$res->content("<html>...</html>\n"); + +my $res2 = $res->clone; +is($res2->code, 200); +is($res2->header("cOntent-TYPE"), "text/html;version=3.0"); +like($res2->content, qr/>\.\.\.</); + +# Check the base method: +$res = HTTP::Response->new(200, "This message"); +is($res->base, undef); +$res->request($req); +$res->content_type("image/gif"); + +is($res->base, "http://www.sn.no/"); +$res->header('Base', 'http://www.sn.no/xxx/'); +is($res->base, "http://www.sn.no/xxx/"); + +# Check the AUTLOAD delegate method with regular expressions +"This string contains text/html" =~ /(\w+\/\w+)/; +$res->content_type($1); +is($res->content_type, "text/html"); + +# Check what happens when passed a new URI object +require URI; +$req = HTTP::Request->new(GET => URI->new("http://localhost")); +is($req->uri, "http://localhost"); + +$req = HTTP::Request->new(GET => "http://www.example.com", + [ Foo => 1, bar => 2 ], "FooBar\n"); +is($req->as_string, <<EOT); +GET http://www.example.com +Bar: 2 +Foo: 1 + +FooBar +EOT + +$req->clear; +is($req->as_string, <<EOT); +GET http://www.example.com + +EOT diff --git a/t/message-parts.t b/t/message-parts.t new file mode 100644 index 0000000..06444ed --- /dev/null +++ b/t/message-parts.t @@ -0,0 +1,149 @@ +use strict; +use warnings; + +use Test::More; +plan tests => 47; + +use HTTP::Message; +use HTTP::Request::Common qw(POST); + +my $m = HTTP::Message->new; + +is(ref($m->headers), "HTTP::Headers"); +is($m->headers_as_string, ""); +is($m->content, ""); +is(j($m->parts), ""); +is($m->as_string, "\n"); + +my $m_clone = $m->clone; +$m->push_header("Foo", 1); +$m->add_content("foo"); + +is($m_clone->as_string, "\n"); +is($m->headers_as_string, "Foo: 1\n"); +is($m->header("Foo"), 1); +is($m->as_string, "Foo: 1\n\nfoo\n"); +is($m->as_string("\r\n"), "Foo: 1\r\n\r\nfoo"); +is(j($m->parts), ""); + +$m->content_type("message/foo"); +$m->content(<<EOT); +H1: 1 +H2: 2 + 3 +H3: abc + +FooBar +EOT + +my @parts = $m->parts; +is(@parts, 1); +my $m2 = $parts[0]; +is(ref($m2), "HTTP::Message"); + +is($m2->header("h1"), 1); +is($m2->header("h2"), "2\n 3"); +is($m2->header("h3"), " abc"); +is($m2->content, "FooBar\n"); +is($m2->as_string, $m->content); +is(j($m2->parts), ""); + +$m = POST("http://www.example.com", + Content_Type => 'form-data', + Content => [ foo => 1, bar => 2 ]); +is($m->content_type, "multipart/form-data"); +@parts = $m->parts; +is(@parts, 2); +is($parts[0]->header("Content-Disposition"), 'form-data; name="foo"'); +is($parts[0]->content, 1); +is($parts[1]->header("Content-Disposition"), 'form-data; name="bar"'); +is($parts[1]->content, 2); + +$m = HTTP::Message->new; +$m->content_type("message/http"); +$m->content(<<EOT); +GET / HTTP/1.0 +Host: example.com + +How is this? +EOT + +@parts = $m->parts; +is(@parts, 1); +is($parts[0]->method, "GET"); +is($parts[0]->uri, "/"); +is($parts[0]->protocol, "HTTP/1.0"); +is($parts[0]->header("Host"), "example.com"); +is($parts[0]->content, "How is this?\n"); + +$m = HTTP::Message->new; +$m->content_type("message/http"); +$m->content(<<EOT); +HTTP/1.1 200 is +Content-Type : text/html + +<H1>Hello world!</H1> +EOT + +@parts = $m->parts; +is(@parts, 1); +is($parts[0]->code, 200); +is($parts[0]->message, "is"); +is($parts[0]->protocol, "HTTP/1.1"); +is($parts[0]->content_type, "text/html"); +is($parts[0]->content, "<H1>Hello world!</H1>\n"); + +$m->parts(HTTP::Request->new("GET", "http://www.example.com")); +is($m->as_string, "Content-Type: message/http\n\nGET http://www.example.com\r\n\r\n"); + +$m = HTTP::Request->new("PUT", "http://www.example.com"); +$m->parts(HTTP::Message->new([Foo => 1], "abc\n"), HTTP::Message->new([Bar => 2], "def")); +is($m->as_string, <<EOT); +PUT http://www.example.com +Content-Type: multipart/mixed; boundary=xYzZY + +--xYzZY\r +Foo: 1\r +\r +abc +\r +--xYzZY\r +Bar: 2\r +\r +def\r +--xYzZY--\r +EOT + +$m->content(<<EOT); +--xYzZY +Content-Length: 4 + +abcd +--xYzZY-- +EOT + +@parts = $m->parts; +is(@parts, 1); +is($parts[0]->content_length, 4); +is($parts[0]->content, "abcd"); + +$m->content(" + +--xYzZY +Content-Length: 4 + +efgh +--xYzZY +Content-Length: 3 + +ijk +--xYzZY--"); + +@parts = $m->parts; +is(@parts, 2); +is($parts[0]->content_length, 4); +is($parts[0]->content, "efgh"); +is($parts[1]->content_length, 3); +is($parts[1]->content, "ijk"); + +sub j { join(":", @_) } diff --git a/t/message.t b/t/message.t new file mode 100644 index 0000000..39691d4 --- /dev/null +++ b/t/message.t @@ -0,0 +1,494 @@ +use strict; +use warnings; + +use Test::More; + +plan tests => 129; + +require HTTP::Message; +use Config qw(%Config); + +my($m, $m2, @parts); + +$m = HTTP::Message->new; +ok($m); +is(ref($m), "HTTP::Message"); +is(ref($m->headers), "HTTP::Headers"); +is($m->as_string, "\n"); +is($m->headers->as_string, ""); +is($m->headers_as_string, ""); +is($m->content, ""); + +$m->header("Foo", 1); +is($m->as_string, "Foo: 1\n\n"); + +$m2 = HTTP::Message->new($m->headers); +$m2->header(bar => 2); +is($m->as_string, "Foo: 1\n\n"); +is($m2->as_string, "Bar: 2\nFoo: 1\n\n"); +is($m2->dump, "Bar: 2\nFoo: 1\n\n(no content)\n"); +is($m2->dump(no_content => ""), "Bar: 2\nFoo: 1\n\n\n"); +is($m2->dump(no_content => "-"), "Bar: 2\nFoo: 1\n\n-\n"); +$m2->content('0'); +is($m2->dump(no_content => "-"), "Bar: 2\nFoo: 1\n\n0\n"); +is($m2->dump(no_content => "0"), "Bar: 2\nFoo: 1\n\n\\x30\n"); + +$m2 = HTTP::Message->new($m->headers, "foo"); +is($m2->as_string, "Foo: 1\n\nfoo\n"); +is($m2->as_string("<<\n"), "Foo: 1<<\n<<\nfoo"); +$m2 = HTTP::Message->new($m->headers, "foo\n"); +is($m2->as_string, "Foo: 1\n\nfoo\n"); + +$m = HTTP::Message->new([a => 1, b => 2], "abc"); +is($m->as_string, "A: 1\nB: 2\n\nabc\n"); + +$m = HTTP::Message->parse(""); +is($m->as_string, "\n"); +$m = HTTP::Message->parse("\n"); +is($m->as_string, "\n"); +$m = HTTP::Message->parse("\n\n"); +is($m->as_string, "\n\n"); +is($m->content, "\n"); + +$m = HTTP::Message->parse("foo"); +is($m->as_string, "\nfoo\n"); +$m = HTTP::Message->parse("foo: 1"); +is($m->as_string, "Foo: 1\n\n"); +$m = HTTP::Message->parse("foo_bar: 1"); +is($m->as_string, "Foo_bar: 1\n\n"); +$m = HTTP::Message->parse("foo: 1\n\nfoo"); +is($m->as_string, "Foo: 1\n\nfoo\n"); +$m = HTTP::Message->parse(<<EOT); +FOO : 1 + 2 + 3 + 4 +bar: + 1 +Baz: 1 + +foobarbaz +EOT +is($m->as_string, <<EOT); +Bar: + 1 +Baz: 1 +FOO: 1 + 2 + 3 + 4 + +foobarbaz +EOT + +$m = HTTP::Message->parse(<<EOT); +Date: Fri, 18 Feb 2005 18:33:46 GMT +Connection: close +Content-Type: text/plain + +foo:bar +second line +EOT +is($m->content(""), <<EOT); +foo:bar +second line +EOT +is($m->as_string, <<EOT); +Connection: close +Date: Fri, 18 Feb 2005 18:33:46 GMT +Content-Type: text/plain + +EOT + +$m = HTTP::Message->parse(" abc\nfoo: 1\n"); +is($m->as_string, "\n abc\nfoo: 1\n"); +$m = HTTP::Message->parse(" foo : 1\n"); +is($m->as_string, "\n foo : 1\n"); +$m = HTTP::Message->parse("\nfoo: bar\n"); +is($m->as_string, "\nfoo: bar\n"); + +$m = HTTP::Message->new([a => 1, b => 2], "abc"); +is($m->content("foo\n"), "abc"); +is($m->content, "foo\n"); + +$m->add_content("bar"); +is($m->content, "foo\nbar"); +$m->add_content(\"\n"); +is($m->content, "foo\nbar\n"); + +is(ref($m->content_ref), "SCALAR"); +is(${$m->content_ref}, "foo\nbar\n"); +${$m->content_ref} =~ s/[ao]/i/g; +is($m->content, "fii\nbir\n"); + +$m->clear; +is($m->headers->header_field_names, 0); +is($m->content, ""); + +is($m->parts, undef); +$m->parts(HTTP::Message->new, + HTTP::Message->new([a => 1], "foo"), + HTTP::Message->new(undef, "bar\n"), + ); +is($m->parts->as_string, "\n"); + +my $str = $m->as_string; +$str =~ s/\r/<CR>/g; +is($str, <<EOT); +Content-Type: multipart/mixed; boundary=xYzZY + +--xYzZY<CR> +<CR> +<CR> +--xYzZY<CR> +A: 1<CR> +<CR> +foo<CR> +--xYzZY<CR> +<CR> +bar +<CR> +--xYzZY--<CR> +EOT + +$m2 = HTTP::Message->new; +$m2->parts($m); + +$str = $m2->as_string; +$str =~ s/\r/<CR>/g; +ok($str =~ /boundary=(\S+)/); + + +is($str, <<EOT); +Content-Type: multipart/mixed; boundary=$1 + +--$1<CR> +Content-Type: multipart/mixed; boundary=xYzZY<CR> +<CR> +--xYzZY<CR> +<CR> +<CR> +--xYzZY<CR> +A: 1<CR> +<CR> +foo<CR> +--xYzZY<CR> +<CR> +bar +<CR> +--xYzZY--<CR> +<CR> +--$1--<CR> +EOT + +@parts = $m2->parts; +is(@parts, 1); + +@parts = $parts[0]->parts; +is(@parts, 3); +is($parts[1]->header("A"), 1); + +$m2->parts([HTTP::Message->new]); +@parts = $m2->parts; +is(@parts, 1); + +$m2->parts([]); +@parts = $m2->parts; +is(@parts, 0); + +$m->clear; +$m2->clear; + +$m = HTTP::Message->new([content_type => "message/http; boundary=aaa", + ], + <<EOT); +GET / HTTP/1.1 +Host: www.example.com:8008 + +EOT + +@parts = $m->parts; +is(@parts, 1); +$m2 = $parts[0]; +is(ref($m2), "HTTP::Request"); +is($m2->method, "GET"); +is($m2->uri, "/"); +is($m2->protocol, "HTTP/1.1"); +is($m2->header("Host"), "www.example.com:8008"); +is($m2->content, ""); + +$m->content(<<EOT); +HTTP/1.0 200 OK +Content-Type: text/plain + +Hello +EOT + +$m2 = $m->parts; +is(ref($m2), "HTTP::Response"); +is($m2->protocol, "HTTP/1.0"); +is($m2->code, "200"); +is($m2->message, "OK"); +is($m2->content_type, "text/plain"); +is($m2->content, "Hello\n"); + +eval { $m->parts(HTTP::Message->new, HTTP::Message->new) }; +ok($@); + +$m->add_part(HTTP::Message->new([a=>[1..3]], "a")); +$str = $m->as_string; +$str =~ s/\r/<CR>/g; +is($str, <<EOT); +Content-Type: multipart/mixed; boundary=xYzZY + +--xYzZY<CR> +Content-Type: message/http; boundary=aaa<CR> +<CR> +HTTP/1.0 200 OK +Content-Type: text/plain + +Hello +<CR> +--xYzZY<CR> +A: 1<CR> +A: 2<CR> +A: 3<CR> +<CR> +a<CR> +--xYzZY--<CR> +EOT + +$m->add_part(HTTP::Message->new([b=>[1..3]], "b")); + +$str = $m->as_string; +$str =~ s/\r/<CR>/g; +is($str, <<EOT); +Content-Type: multipart/mixed; boundary=xYzZY + +--xYzZY<CR> +Content-Type: message/http; boundary=aaa<CR> +<CR> +HTTP/1.0 200 OK +Content-Type: text/plain + +Hello +<CR> +--xYzZY<CR> +A: 1<CR> +A: 2<CR> +A: 3<CR> +<CR> +a<CR> +--xYzZY<CR> +B: 1<CR> +B: 2<CR> +B: 3<CR> +<CR> +b<CR> +--xYzZY--<CR> +EOT + +$m = HTTP::Message->new; +$m->add_part(HTTP::Message->new([a=>[1..3]], "a")); +is($m->header("Content-Type"), "multipart/mixed; boundary=xYzZY"); +$str = $m->as_string; +$str =~ s/\r/<CR>/g; +is($str, <<EOT); +Content-Type: multipart/mixed; boundary=xYzZY + +--xYzZY<CR> +A: 1<CR> +A: 2<CR> +A: 3<CR> +<CR> +a<CR> +--xYzZY--<CR> +EOT + +$m = HTTP::Message->new; +$m->content_ref(\my $foo); +is($m->content_ref, \$foo); +$foo = "foo"; +is($m->content, "foo"); +$m->add_content("bar"); +is($foo, "foobar"); +is($m->as_string, "\nfoobar\n"); +$m->content_type("message/foo"); +$m->parts(HTTP::Message->new(["h", "v"], "C")); +is($foo, "H: v\r\n\r\nC"); +$foo =~ s/C/c/; +$m2 = $m->parts; +is($m2->content, "c"); + +$m = HTTP::Message->new; +$foo = []; +$m->content($foo); +is($m->content, $foo); +is(${$m->content_ref}, $foo); +is(${$m->content_ref([])}, $foo); +isnt($m->content_ref, $foo); +eval {$m->add_content("x")}; +like($@, qr/^Can't append to ARRAY content/); + +$foo = sub { "foo" }; +$m->content($foo); +is($m->content, $foo); +is(${$m->content_ref}, $foo); + +$m->content_ref($foo); +is($m->content, $foo); +is($m->content_ref, $foo); + +eval {$m->content_ref("foo")}; +like($@, qr/^Setting content_ref to a non-ref/); + +$m->content_ref(\"foo"); +eval {$m->content("bar")}; +like($@, qr/^Modification of a read-only value/); + +$foo = "foo"; +$m->content_ref(\$foo); +is($m->content("bar"), "foo"); +is($foo, "bar"); +is($m->content, "bar"); +is($m->content_ref, \$foo); + +$m = HTTP::Message->new; +$m->content("fo=6F"); +is($m->decoded_content, "fo=6F"); +$m->header("Content-Encoding", "quoted-printable"); +is($m->decoded_content, "foo"); + +$m = HTTP::Message->new; +$m->header("Content-Encoding", "gzip, base64"); +$m->content_type("text/plain; charset=UTF-8"); +$m->content("H4sICFWAq0ECA3h4eAB7v3u/R6ZCSUZqUarCoxm7uAAZKHXiEAAAAA==\n"); + +$@ = ""; +is(eval { $m->decoded_content }, "\x{FEFF}Hi there \x{263A}\n"); +is($@ || "", ""); +is($m->content, "H4sICFWAq0ECA3h4eAB7v3u/R6ZCSUZqUarCoxm7uAAZKHXiEAAAAA==\n"); + +$m2 = $m->clone; +ok($m2->decode); +is($m2->header("Content-Encoding"), undef); +like($m2->content, qr/Hi there/); + +ok(grep { $_ eq "gzip" } $m->decodable); + +my $tmp = MIME::Base64::decode($m->content); +$m->content($tmp); +$m->header("Content-Encoding", "gzip"); +$@ = ""; +is(eval { $m->decoded_content }, "\x{FEFF}Hi there \x{263A}\n"); +is($@ || "", ""); +is($m->content, $tmp); + +$m->remove_header("Content-Encoding"); +$m->content("a\xFF"); + +is($m->decoded_content, "a\x{FFFD}"); +is($m->decoded_content(charset_strict => 1), undef); + +$m->header("Content-Encoding", "foobar"); +is($m->decoded_content, undef); +like($@, qr/^Don't know how to decode Content-Encoding 'foobar'/); + +my $err = 0; +eval { + $m->decoded_content(raise_error => 1); + $err++; +}; +like($@, qr/Don't know how to decode Content-Encoding 'foobar'/); +is($err, 0); + +eval { + HTTP::Message->new([], "\x{263A}"); +}; +like($@, qr/bytes/); +$m = HTTP::Message->new; +eval { + $m->add_content("\x{263A}"); +}; +like($@, qr/bytes/); +eval { + $m->content("\x{263A}"); +}; +like($@, qr/bytes/); + +# test the add_content_utf8 method +$m = HTTP::Message->new(["Content-Type", "text/plain; charset=UTF-8"]); +$m->add_content_utf8("\x{263A}"); +$m->add_content_utf8("-\xC5"); +is($m->content, "\xE2\x98\xBA-\xC3\x85"); +is($m->decoded_content, "\x{263A}-\x{00C5}"); + +$m = HTTP::Message->new([ + "Content-Type", "text/plain", + ], + "Hello world!" +); +$m->content_length(length $m->content); +$m->encode("deflate"); +$m->dump(prefix => "# "); +is($m->dump(prefix => "| "), <<'EOT'); +| Content-Encoding: deflate +| Content-Type: text/plain +| +| x\x9C\xF3H\xCD\xC9\xC9W(\xCF/\xCAIQ\4\0\35\t\4^ +EOT +$m->encode("base64", "identity"); +is($m->as_string, <<'EOT'); +Content-Encoding: deflate, base64, identity +Content-Type: text/plain + +eJzzSM3JyVcozy/KSVEEAB0JBF4= +EOT +is($m->decoded_content, "Hello world!"); + +# Raw RFC 1951 deflate +$m = HTTP::Message->new([ + "Content-Type" => "text/plain", + "Content-Encoding" => "deflate, base64", + ], + "80jNyclXCM8vyklRBAA=" + ); +is($m->decoded_content, "Hello World!"); +ok(!$m->header("Client-Warning")); + + +if (eval "require IO::Uncompress::Bunzip2") { + $m = HTTP::Message->new([ + "Content-Type" => "text/plain", + "Content-Encoding" => "x-bzip2, base64", + ], + "QlpoOTFBWSZTWcvLx0QAAAHVgAAQYAAAQAYEkIAgADEAMCBoYlnQeSEMvxdyRThQkMvLx0Q=\n" + ); + is($m->decoded_content, "Hello world!\n"); + ok($m->decode); + is($m->content, "Hello world!\n"); + + if (eval "require IO::Compress::Bzip2") { + $m = HTTP::Message->new([ + "Content-Type" => "text/plain", + ], + "Hello world!" + ); + ok($m->encode("x-bzip2")); + is($m->header("Content-Encoding"), "x-bzip2"); + like($m->content, qr/^BZh.*\0/); + is($m->decoded_content, "Hello world!"); + ok($m->decode); + is($m->content, "Hello world!"); + } + else { + skip("Need IO::Compress::Bzip2", undef) for 1..6; + } +} +else { + skip("Need IO::Uncompress::Bunzip2", undef) for 1..9; +} + +# test decoding of XML content +$m = HTTP::Message->new(["Content-Type", "application/xml"], "\xFF\xFE<\0?\0x\0m\0l\0 \0v\0e\0r\0s\0i\0o\0n\0=\0\"\x001\0.\x000\0\"\0 \0e\0n\0c\0o\0d\0i\0n\0g\0=\0\"\0U\0T\0F\0-\x001\x006\0l\0e\0\"\0?\0>\0\n\0<\0r\0o\0o\0t\0>\0\xC9\0r\0i\0c\0<\0/\0r\0o\0o\0t\0>\0\n\0"); +is($m->decoded_content, "<?xml version=\"1.0\"?>\n<root>\xC9ric</root>\n"); diff --git a/t/request.t b/t/request.t new file mode 100644 index 0000000..44c3868 --- /dev/null +++ b/t/request.t @@ -0,0 +1,33 @@ +# Test extra HTTP::Request methods. Basic operation is tested in the +# message.t test suite. + +use strict; +use warnings; + +use Test::More; +plan tests => 11; + +use HTTP::Request; + +my $req = HTTP::Request->new(GET => "http://www.example.com"); +$req->accept_decodable; + +is($req->method, "GET"); +is($req->uri, "http://www.example.com"); +like($req->header("Accept-Encoding"), qr/\bgzip\b/); # assuming IO::Uncompress::Gunzip is there + +$req->dump(prefix => "# "); + +is($req->method("DELETE"), "GET"); +is($req->method, "DELETE"); + +is($req->uri("http:"), "http://www.example.com"); +is($req->uri, "http:"); + +$req->protocol("HTTP/1.1"); + +my $r2 = HTTP::Request->parse($req->as_string); +is($r2->method, "DELETE"); +is($r2->uri, "http:"); +is($r2->protocol, "HTTP/1.1"); +is($r2->header("Accept-Encoding"), $req->header("Accept-Encoding")); diff --git a/t/request_type_with_data.t b/t/request_type_with_data.t new file mode 100644 index 0000000..71cd733 --- /dev/null +++ b/t/request_type_with_data.t @@ -0,0 +1,22 @@ +use strict; +use warnings; + +use Test::More 0.88; +use HTTP::Request::Common; + +# I'd use Test::Warnings here, but let's respect our downstream consumers and +# not force that prereq on them +my @warnings; +$SIG{__WARN__} = sub { push @warnings, grep { length } @_ }; + +my $request = HTTP::Request::Common::request_type_with_data( + 'POST' => 'https://localhost/', + 'content_type' => 'multipart/form-data; boundary=----1234', + 'content' => [ a => 1, b => undef ], +); + +isa_ok($request, 'HTTP::Request'); +is(scalar(@warnings), 0, 'no warnings') + or diag('got warnings: ', explain(\@warnings)); + +done_testing; diff --git a/t/response.t b/t/response.t new file mode 100644 index 0000000..154ae37 --- /dev/null +++ b/t/response.t @@ -0,0 +1,102 @@ +# Test extra HTTP::Response methods. Basic operation is tested in the +# message.t test suite. + +use strict; +use warnings; + +use Test::More; +plan tests => 23; + +use HTTP::Date; +use HTTP::Request; +use HTTP::Response; + +my $time = time; + +my $req = HTTP::Request->new(GET => 'http://www.sn.no'); +$req->date($time - 30); + +my $r = new HTTP::Response 200, "OK"; +$r->client_date($time - 20); +$r->date($time - 25); +$r->last_modified($time - 5000000); +$r->request($req); + +#print $r->as_string; + +my $current_age = $r->current_age; + +ok($current_age >= 35 && $current_age <= 40); + +my $freshness_lifetime = $r->freshness_lifetime; +ok($freshness_lifetime >= 12 * 3600); +is($r->freshness_lifetime(heuristic_expiry => 0), undef); + +my $is_fresh = $r->is_fresh; +ok($is_fresh); +is($r->is_fresh(heuristic_expiry => 0), undef); + +print "# current_age = $current_age\n"; +print "# freshness_lifetime = $freshness_lifetime\n"; +print "# response is "; +print " not " unless $is_fresh; +print "fresh\n"; + +print "# it will be fresh for "; +print $freshness_lifetime - $current_age; +print " more seconds\n"; + +# OK, now we add an Expires header +$r->expires($time); +print "\n", $r->dump(prefix => "# "); + +$freshness_lifetime = $r->freshness_lifetime; +is($freshness_lifetime, 25); +$r->remove_header('expires'); + +# Now we try the 'Age' header and the Cache-Contol: +$r->header('Age', 300); +$r->push_header('Cache-Control', 'junk'); +$r->push_header(Cache_Control => 'max-age = 10'); + +#print $r->as_string; + +$current_age = $r->current_age; +$freshness_lifetime = $r->freshness_lifetime; + +print "# current_age = $current_age\n"; +print "# freshness_lifetime = $freshness_lifetime\n"; + +ok($current_age >= 300); +is($freshness_lifetime, 10); + +ok($r->fresh_until); # should return something +ok($r->fresh_until(heuristic_expiry => 0)); # should return something + +my $r2 = HTTP::Response->parse($r->as_string); +my @h = $r2->header('Cache-Control'); +is(@h, 2); + +$r->remove_header("Cache-Control"); + +ok($r->fresh_until); # should still return something +is($r->fresh_until(heuristic_expiry => 0), undef); + +is($r->redirects, 0); +$r->previous($r2); +is($r->previous, $r2); +is($r->redirects, 1); + +$r2->previous($r->clone); +is($r->redirects, 2); +for ($r->redirects) { + ok($_->is_success); +} + +is($r->base, $r->request->uri); +$r->push_header("Content-Location", "/1/A/a"); +is($r->base, "http://www.sn.no/1/A/a"); +$r->push_header("Content-Base", "/2/;a=/foo/bar"); +is($r->base, "http://www.sn.no/2/;a=/foo/bar"); +$r->push_header("Content-Base", "/3/"); +is($r->base, "http://www.sn.no/2/;a=/foo/bar"); diff --git a/t/status-old.t b/t/status-old.t new file mode 100644 index 0000000..bc48a89 --- /dev/null +++ b/t/status-old.t @@ -0,0 +1,19 @@ +use strict; +use warnings; + +use Test::More; +plan tests => 8; + +use HTTP::Status; + +is(RC_OK, 200); + +ok(is_info(RC_CONTINUE)); +ok(is_success(RC_ACCEPTED)); +ok(is_error(RC_BAD_REQUEST)); +ok(is_redirect(RC_MOVED_PERMANENTLY)); + +ok(!is_success(RC_NOT_FOUND)); + +is(status_message(0), undef); +is(status_message(200), "OK"); diff --git a/t/status.t b/t/status.t new file mode 100644 index 0000000..42d7465 --- /dev/null +++ b/t/status.t @@ -0,0 +1,21 @@ +use strict; +use warnings; + +use Test::More; +plan tests => 10; + +use HTTP::Status qw(:constants :is status_message); + +is(HTTP_OK, 200); + +ok(is_info(HTTP_CONTINUE)); +ok(is_success(HTTP_ACCEPTED)); +ok(is_error(HTTP_BAD_REQUEST)); +ok(is_client_error(HTTP_I_AM_A_TEAPOT)); +ok(is_redirect(HTTP_MOVED_PERMANENTLY)); +ok(is_redirect(HTTP_PERMANENT_REDIRECT)); + +ok(!is_success(HTTP_NOT_FOUND)); + +is(status_message(0), undef); +is(status_message(200), "OK"); |