summaryrefslogtreecommitdiff
path: root/t
diff options
context:
space:
mode:
authorLorry Tar Creator <lorry-tar-importer@lorry>2015-07-19 17:50:38 +0000
committerLorry Tar Creator <lorry-tar-importer@lorry>2015-07-19 17:50:38 +0000
commitd403562e3f7ac96df7cee2c1709ecd970b6c9761 (patch)
tree0c8ec1bc7a6e0bf408a0e183b52ef7de174cde9a /t
downloadHTTP-Message-tarball-master.tar.gz
Diffstat (limited to 't')
-rw-r--r--t/common-req.t235
-rw-r--r--t/headers-auth.t41
-rw-r--r--t/headers-etag.t29
-rw-r--r--t/headers-util.t45
-rw-r--r--t/headers.t480
-rw-r--r--t/http-config.t85
-rw-r--r--t/message-charset.t124
-rw-r--r--t/message-decode-xml.t33
-rw-r--r--t/message-old.t97
-rw-r--r--t/message-parts.t149
-rw-r--r--t/message.t494
-rw-r--r--t/request.t33
-rw-r--r--t/request_type_with_data.t22
-rw-r--r--t/response.t102
-rw-r--r--t/status-old.t19
-rw-r--r--t/status.t21
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");