diff --git a/t/mojo/request.t b/t/mojo/request.t index ea5ebd98f0..23cd1864aa 100644 --- a/t/mojo/request.t +++ b/t/mojo/request.t @@ -9,294 +9,310 @@ use Mojo::Message::Request; use Mojo::URL; use Mojo::Util qw(encode gzip); -# Defaults -my $req = Mojo::Message::Request->new; -is $req->max_message_size, 16777216, 'right default'; - -# Request ID -my $id = Mojo::Message::Request->new->request_id; -ok length $id >= 8, 'at least 8 characters'; -my $id2 = Mojo::Message::Request->new->request_id; -ok length $id >= 8, 'at least 8 characters'; -isnt $id, $id2, 'different id'; - -# Parse HTTP 1.1 message with huge "Cookie" header exceeding all limits -$req = Mojo::Message::Request->new; -my $finished; -$req->max_message_size($req->headers->max_line_size); -my $huge = 'a=b; ' x $req->max_message_size; -$req->on(finish => sub { $finished = shift->is_finished }); -ok !$req->is_limit_exceeded, 'limit is not exceeded'; -$req->parse("PUT /upload HTTP/1.1\x0d\x0aCookie: $huge\x0d\x0a"); -ok $req->is_limit_exceeded, 'limit is exceeded'; -$req->parse("Content-Length: 0\x0d\x0a\x0d\x0a"); -ok $finished, 'finish event has been emitted'; -ok $req->is_finished, 'request is finished'; -is $req->content->leftovers, '', 'no leftovers'; -is $req->error->{message}, 'Maximum message size exceeded', 'right error'; -ok $req->is_limit_exceeded, 'limit is exceeded'; -is $req->method, 'PUT', 'right method'; -is $req->version, '1.1', 'right version'; -is $req->url, '/upload', 'right URL'; -is $req->cookie('a'), undef, 'no value'; - -# Parse HTTP 1.1 message with huge "Cookie" header exceeding line limit -$req = Mojo::Message::Request->new; -is $req->headers->max_line_size, 8192, 'right size'; -is $req->headers->max_lines, 100, 'right number'; -$req->parse("GET / HTTP/1.1\x0d\x0a"); -$req->parse("Cookie: @{['a=b; ' x 131072]}\x0d\x0a"); -$req->parse("Content-Length: 0\x0d\x0a\x0d\x0a"); -ok $req->is_finished, 'request is finished'; -is $req->error->{message}, 'Maximum header size exceeded', 'right error'; -ok $req->is_limit_exceeded, 'limit is exceeded'; -is $req->method, 'GET', 'right method'; -is $req->version, '1.1', 'right version'; -is $req->url, '/', 'right URL'; -is $req->cookie('a'), undef, 'no value'; -is $req->body, '', 'no content'; - -# Parse HTTP 1.1 message with huge "Cookie" header exceeding line limit -# (alternative) -$req = Mojo::Message::Request->new; -$req->parse("GET / HTTP/1.1\x0d\x0a"); -$req->parse("Content-Length: 4\x0d\x0aCookie: @{['a=b; ' x 131072]}\x0d\x0a" . "X-Test: 23\x0d\x0a\x0d\x0aabcd"); -ok $req->is_finished, 'request is finished'; -is $req->error->{message}, 'Maximum header size exceeded', 'right error'; -ok $req->is_limit_exceeded, 'limit is exceeded'; -is $req->method, 'GET', 'right method'; -is $req->version, '1.1', 'right version'; -is $req->url, '/', 'right URL'; -is $req->cookie('a'), undef, 'no value'; -is $req->body, '', 'no content'; - -# Parse HTTP 1.1 message with content exceeding line limit -$req = Mojo::Message::Request->new; -is $req->max_message_size, 16777216, 'right size'; -$req->parse("GET / HTTP/1.1\x0d\x0a"); -$req->parse("Content-Length: 655360\x0d\x0a\x0d\x0a@{['a=b; ' x 131072]}"); -ok $req->is_finished, 'request is finished'; -is $req->error, undef, 'no error'; -is $req->method, 'GET', 'right method'; -is $req->version, '1.1', 'right version'; -is $req->url, '/', 'right URL'; -is $req->body, 'a=b; ' x 131072, 'right content'; - -# Parse broken start-line -$req = Mojo::Message::Request->new; -$req->parse("12345\x0d\x0a"); -ok $req->is_finished, 'request is finished'; -is $req->error->{message}, 'Bad request start-line', 'right error'; -ok !$req->is_limit_exceeded, 'limit is not exceeded'; - -# Parse broken HTTP 1.1 message with header exceeding line limit -$req = Mojo::Message::Request->new; -$req->parse("GET / HTTP/1.1\x0d\x0a"); -$req->parse("Content-Length: 0\x0d\x0a"); -ok !$req->is_limit_exceeded, 'limit is not exceeded'; -$req->parse("Foo: @{['a' x 8192]}"); -ok $req->is_finished, 'request is finished'; -is $req->error->{message}, 'Maximum header size exceeded', 'right error'; -ok $req->is_limit_exceeded, 'limit is exceeded'; -is $req->method, 'GET', 'right method'; -is $req->version, '1.1', 'right version'; -is $req->url, '/', 'right URL'; -is $req->headers->header('Foo'), undef, 'no "Foo" value'; -is $req->body, '', 'no content'; - -# Parse broken HTTP 1.1 message with start-line exceeding line limit -$req = Mojo::Message::Request->new; -is $req->max_line_size, 8192, 'right size'; -is $req->headers->max_lines, 100, 'right number'; -$req->parse("GET /@{['abcd' x 131072]} HTTP/1.1"); -ok $req->is_finished, 'request is finished'; -is $req->error->{message}, 'Maximum start-line size exceeded', 'right error'; -is $req->method, 'GET', 'right method'; -is $req->version, '1.1', 'right version'; -is $req->url, '', 'no URL'; -is $req->cookie('a'), undef, 'no value'; -is $req->body, '', 'no content'; - -# Parse broken HTTP 1.1 message with start-line exceeding line limit -# (alternative) -$req = Mojo::Message::Request->new; -$req->parse('GET /'); -$req->parse('abcd' x 131072); -ok $req->is_finished, 'request is finished'; -is $req->error->{message}, 'Maximum start-line size exceeded', 'right error'; -is $req->method, 'GET', 'right method'; -is $req->version, '1.1', 'right version'; -is $req->url, '', 'no URL'; -is $req->cookie('a'), undef, 'no value'; -is $req->body, '', 'no content'; - -# Parse pipelined HTTP 1.1 messages exceeding leftover limit -$req = Mojo::Message::Request->new; -is $req->content->max_leftover_size, 262144, 'right size'; -$req->parse("GET /one HTTP/1.1\x0d\x0a"); -$req->parse("Content-Length: 120000\x0d\x0a\x0d\x0a@{['a' x 120000]}"); -ok $req->is_finished, 'request is finished'; -is $req->content->leftovers, '', 'no leftovers'; -$req->parse("GET /two HTTP/1.1\x0d\x0a"); -$req->parse("Content-Length: 120000\x0d\x0a\x0d\x0a@{['b' x 120000]}"); -is length($req->content->leftovers), 120045, 'right size'; -$req->parse("GET /three HTTP/1.1\x0d\x0a"); -$req->parse("Content-Length: 120000\x0d\x0a\x0d\x0a@{['c' x 120000]}"); -is length($req->content->leftovers), 240092, 'right size'; -$req->parse("GET /four HTTP/1.1\x0d\x0a"); -$req->parse("Content-Length: 120000\x0d\x0a\x0d\x0a@{['d' x 120000]}"); -is length($req->content->leftovers), 360138, 'right size'; -$req->parse("GET /five HTTP/1.1\x0d\x0a"); -$req->parse("Content-Length: 120000\x0d\x0a\x0d\x0a@{['e' x 120000]}"); -is length($req->content->leftovers), 360138, 'right size'; -is $req->error, undef, 'no error'; -is $req->method, 'GET', 'right method'; -is $req->version, '1.1', 'right version'; -is $req->url, '/one', 'right URL'; -is $req->body, 'a' x 120000, 'right content'; - -# Parse pipelined HTTP 1.1 messages exceeding leftover limit (chunked) -$req = Mojo::Message::Request->new; -$req->parse("GET /one HTTP/1.1\x0d\x0a"); -$req->parse("Transfer-Encoding: chunked\x0d\x0a\x0d\x0a"); -$req->parse("ea60\x0d\x0a"); -$req->parse('a' x 60000); -$req->parse("\x0d\x0aea60\x0d\x0a"); -$req->parse('a' x 60000); -$req->parse("\x0d\x0a0\x0d\x0a\x0d\x0a"); -ok $req->is_finished, 'request is finished'; -is $req->content->leftovers, '', 'no leftovers'; -$req->parse("GET /two HTTP/1.1\x0d\x0a"); -$req->parse("Content-Length: 120000\x0d\x0a\x0d\x0a@{['b' x 120000]}"); -is length($req->content->leftovers), 120045, 'right size'; -$req->parse("GET /three HTTP/1.1\x0d\x0a"); -$req->parse("Content-Length: 120000\x0d\x0a\x0d\x0a@{['c' x 120000]}"); -is length($req->content->leftovers), 240092, 'right size'; -$req->parse("GET /four HTTP/1.1\x0d\x0a"); -$req->parse("Content-Length: 120000\x0d\x0a\x0d\x0a@{['d' x 120000]}"); -is length($req->content->leftovers), 360138, 'right size'; -$req->parse("GET /five HTTP/1.1\x0d\x0a"); -$req->parse("Content-Length: 120000\x0d\x0a\x0d\x0a@{['e' x 120000]}"); -is length($req->content->leftovers), 360138, 'right size'; -is $req->error, undef, 'no error'; -is $req->method, 'GET', 'right method'; -is $req->version, '1.1', 'right version'; -is $req->url, '/one', 'right URL'; -is $req->body, 'a' x 120000, 'right content'; - -# Parse HTTP 1.1 start-line, no headers and body -$req = Mojo::Message::Request->new; -$req->parse("GET / HTTP/1.1\x0d\x0a\x0d\x0a"); -ok $req->is_finished, 'request is finished'; -is $req->method, 'GET', 'right method'; -is $req->version, '1.1', 'right version'; -is $req->url, '/', 'right URL'; - -# Parse HTTP 1.1 start-line, no headers and body (small chunks) -$req = Mojo::Message::Request->new; -$req->parse('G'); -ok !$req->is_finished, 'request is not finished'; -$req->parse('E'); -ok !$req->is_finished, 'request is not finished'; -$req->parse('T'); -ok !$req->is_finished, 'request is not finished'; -$req->parse(' '); -ok !$req->is_finished, 'request is not finished'; -$req->parse('/'); -ok !$req->is_finished, 'request is not finished'; -$req->parse(' '); -ok !$req->is_finished, 'request is not finished'; -$req->parse('H'); -ok !$req->is_finished, 'request is not finished'; -$req->parse('T'); -ok !$req->is_finished, 'request is not finished'; -$req->parse('T'); -ok !$req->is_finished, 'request is not finished'; -$req->parse('P'); -ok !$req->is_finished, 'request is not finished'; -$req->parse('/'); -ok !$req->is_finished, 'request is not finished'; -$req->parse('1'); -ok !$req->is_finished, 'request is not finished'; -$req->parse('.'); -ok !$req->is_finished, 'request is not finished'; -$req->parse('1'); -ok !$req->is_finished, 'request is not finished'; -$req->parse("\x0d"); -ok !$req->is_finished, 'request is not finished'; -$req->parse("\x0a"); -ok !$req->is_finished, 'request is not finished'; -$req->parse("\x0d"); -ok !$req->is_finished, 'request is not finished'; -$req->parse("\x0a"); -ok $req->is_finished, 'request is finished'; -is $req->method, 'GET', 'right method'; -is $req->version, '1.1', 'right version'; -is $req->url, '/', 'right URL'; - -# Parse pipelined HTTP 1.1 start-line, no headers and body -$req = Mojo::Message::Request->new; -$req->parse("GET / HTTP/1.1\x0d\x0a\x0d\x0aGET / HTTP/1.1\x0d\x0a\x0d\x0a"); -ok $req->is_finished, 'request is finished'; -is $req->content->leftovers, "GET / HTTP/1.1\x0d\x0a\x0d\x0a", 'second request in leftovers'; - -# Parse HTTP 1.1 start-line, no headers and body with leading CRLFs -$req = Mojo::Message::Request->new; -$req->parse("\x0d\x0a GET / HTTP/1.1\x0d\x0a\x0d\x0a"); -ok $req->is_finished, 'request is finished'; -is $req->method, 'GET', 'right method'; -is $req->version, '1.1', 'right version'; -is $req->url, '/', 'right URL'; - -# Parse WebSocket handshake request -$req = Mojo::Message::Request->new; -$req->parse("GET /demo HTTP/1.1\x0d\x0a"); -$req->parse("Host: example.com\x0d\x0a"); -$req->parse("Connection: Upgrade\x0d\x0a"); -$req->parse("Sec-WebSocket-Key: abcdef=\x0d\x0a"); -$req->parse("Sec-WebSocket-Protocol: sample\x0d\x0a"); -$req->parse("Upgrade: websocket\x0d\x0a\x0d\x0a"); -ok $req->is_finished, 'request is finished'; -ok $req->is_handshake, 'request is WebSocket handshake'; -is $req->method, 'GET', 'right method'; -is $req->version, '1.1', 'right version'; -is $req->url, '/demo', 'right URL'; -is $req->headers->host, 'example.com', 'right "Host" value'; -is $req->headers->connection, 'Upgrade', 'right "Connection" value'; -is $req->headers->sec_websocket_protocol, 'sample', 'right "Sec-WebSocket-Protocol" value'; -is $req->headers->upgrade, 'websocket', 'right "Upgrade" value'; -is $req->headers->sec_websocket_key, 'abcdef=', 'right "Sec-WebSocket-Key" value'; -is $req->body, '', 'no content'; - -# Parse HTTP 1.0 start-line and headers, no body -$req = Mojo::Message::Request->new; -$req->parse("GET /foo/bar/baz.html HTTP/1.0\x0d\x0a"); -$req->parse("Content-Type: text/plain;charset=UTF-8\x0d\x0a"); -$req->parse("Content-Length: 0\x0d\x0a\x0d\x0a"); -ok $req->is_finished, 'request is finished'; -ok !$req->is_handshake, 'request is not a WebSocket handshake'; -is $req->method, 'GET', 'right method'; -is $req->version, '1.0', 'right version'; -is $req->url, '/foo/bar/baz.html', 'right URL'; -is $req->headers->content_type, 'text/plain;charset=UTF-8', 'right "Content-Type" value'; -is $req->headers->content_length, 0, 'right "Content-Length" value'; -is $req->content->charset, 'UTF-8', 'right charset'; - -# Parse HTTP 1.0 start-line and headers, no body (missing Content-Length) -$req = Mojo::Message::Request->new; -$req->parse("GET /foo/bar/baz.html HTTP/1.0\x0d\x0a"); -$req->parse("Content-Type: text/plain\x0d\x0a\x0d\x0a"); -ok $req->is_finished, 'request is finished'; -is $req->method, 'GET', 'right method'; -is $req->version, '1.0', 'right version'; -is $req->url, '/foo/bar/baz.html', 'right URL'; -is $req->headers->content_type, 'text/plain', 'right "Content-Type" value'; -is $req->headers->content_length, undef, 'no "Content-Length" value'; - -# Parse full HTTP 1.0 request (file storage) -{ +subtest 'Defaults' => sub { + my $req = Mojo::Message::Request->new; + is $req->max_message_size, 16777216, 'right default'; +}; + +subtest 'Request ID' => sub { + my $id = Mojo::Message::Request->new->request_id; + ok length $id >= 8, 'at least 8 characters'; + my $id2 = Mojo::Message::Request->new->request_id; + ok length $id >= 8, 'at least 8 characters'; + isnt $id, $id2, 'different id'; +}; + +subtest 'Parse HTTP 1.1 message with huge "Cookie" header exceeding all limits' => sub { + my $req = Mojo::Message::Request->new; + my $finished; + $req->max_message_size($req->headers->max_line_size); + my $huge = 'a=b; ' x $req->max_message_size; + $req->on(finish => sub { $finished = shift->is_finished }); + ok !$req->is_limit_exceeded, 'limit is not exceeded'; + $req->parse("PUT /upload HTTP/1.1\x0d\x0aCookie: $huge\x0d\x0a"); + ok $req->is_limit_exceeded, 'limit is exceeded'; + $req->parse("Content-Length: 0\x0d\x0a\x0d\x0a"); + ok $finished, 'finish event has been emitted'; + ok $req->is_finished, 'request is finished'; + is $req->content->leftovers, '', 'no leftovers'; + is $req->error->{message}, 'Maximum message size exceeded', 'right error'; + ok $req->is_limit_exceeded, 'limit is exceeded'; + is $req->method, 'PUT', 'right method'; + is $req->version, '1.1', 'right version'; + is $req->url, '/upload', 'right URL'; + is $req->cookie('a'), undef, 'no value'; +}; + +subtest 'Parse HTTP 1.1 message with huge "Cookie" header exceeding line limit' => sub { + my $req = Mojo::Message::Request->new; + is $req->headers->max_line_size, 8192, 'right size'; + is $req->headers->max_lines, 100, 'right number'; + $req->parse("GET / HTTP/1.1\x0d\x0a"); + $req->parse("Cookie: @{['a=b; ' x 131072]}\x0d\x0a"); + $req->parse("Content-Length: 0\x0d\x0a\x0d\x0a"); + ok $req->is_finished, 'request is finished'; + is $req->error->{message}, 'Maximum header size exceeded', 'right error'; + ok $req->is_limit_exceeded, 'limit is exceeded'; + is $req->method, 'GET', 'right method'; + is $req->version, '1.1', 'right version'; + is $req->url, '/', 'right URL'; + is $req->cookie('a'), undef, 'no value'; + is $req->body, '', 'no content'; +}; + +subtest 'Parse HTTP 1.1 message with huge "Cookie" header exceeding line limit (alternative)' => sub { + my $req = Mojo::Message::Request->new; + $req->parse("GET / HTTP/1.1\x0d\x0a"); + $req->parse("Content-Length: 4\x0d\x0aCookie: @{['a=b; ' x 131072]}\x0d\x0a" . "X-Test: 23\x0d\x0a\x0d\x0aabcd"); + ok $req->is_finished, 'request is finished'; + is $req->error->{message}, 'Maximum header size exceeded', 'right error'; + ok $req->is_limit_exceeded, 'limit is exceeded'; + is $req->method, 'GET', 'right method'; + is $req->version, '1.1', 'right version'; + is $req->url, '/', 'right URL'; + is $req->cookie('a'), undef, 'no value'; + is $req->body, '', 'no content'; +}; + +subtest 'Parse HTTP 1.1 message with content exceeding line limit' => sub { + my $req = Mojo::Message::Request->new; + is $req->max_message_size, 16777216, 'right size'; + $req->parse("GET / HTTP/1.1\x0d\x0a"); + $req->parse("Content-Length: 655360\x0d\x0a\x0d\x0a@{['a=b; ' x 131072]}"); + ok $req->is_finished, 'request is finished'; + is $req->error, undef, 'no error'; + is $req->method, 'GET', 'right method'; + is $req->version, '1.1', 'right version'; + is $req->url, '/', 'right URL'; + is $req->body, 'a=b; ' x 131072, 'right content'; +}; + +subtest 'Parse broken start-line' => sub { + my $req = Mojo::Message::Request->new; + $req->parse("12345\x0d\x0a"); + ok $req->is_finished, 'request is finished'; + is $req->error->{message}, 'Bad request start-line', 'right error'; + ok !$req->is_limit_exceeded, 'limit is not exceeded'; +}; + +subtest 'Parse broken HTTP 1.1 message with header exceeding line limit' => sub { + my $req = Mojo::Message::Request->new; + $req->parse("GET / HTTP/1.1\x0d\x0a"); + $req->parse("Content-Length: 0\x0d\x0a"); + ok !$req->is_limit_exceeded, 'limit is not exceeded'; + $req->parse("Foo: @{['a' x 8192]}"); + ok $req->is_finished, 'request is finished'; + is $req->error->{message}, 'Maximum header size exceeded', 'right error'; + ok $req->is_limit_exceeded, 'limit is exceeded'; + is $req->method, 'GET', 'right method'; + is $req->version, '1.1', 'right version'; + is $req->url, '/', 'right URL'; + is $req->headers->header('Foo'), undef, 'no "Foo" value'; + is $req->body, '', 'no content'; +}; + +subtest 'Parse broken HTTP 1.1 message with start-line exceeding line limit' => sub { + my $req = Mojo::Message::Request->new; + is $req->max_line_size, 8192, 'right size'; + is $req->headers->max_lines, 100, 'right number'; + $req->parse("GET /@{['abcd' x 131072]} HTTP/1.1"); + ok $req->is_finished, 'request is finished'; + is $req->error->{message}, 'Maximum start-line size exceeded', 'right error'; + is $req->method, 'GET', 'right method'; + is $req->version, '1.1', 'right version'; + is $req->url, '', 'no URL'; + is $req->cookie('a'), undef, 'no value'; + is $req->body, '', 'no content'; +}; + +subtest 'Parse broken HTTP 1.1 message with start-line exceeding line limit (alternative)' => sub { + my $req = Mojo::Message::Request->new; + $req->parse('GET /'); + $req->parse('abcd' x 131072); + ok $req->is_finished, 'request is finished'; + is $req->error->{message}, 'Maximum start-line size exceeded', 'right error'; + is $req->method, 'GET', 'right method'; + is $req->version, '1.1', 'right version'; + is $req->url, '', 'no URL'; + is $req->cookie('a'), undef, 'no value'; + is $req->body, '', 'no content'; +}; + +subtest 'Parse pipelined HTTP 1.1 messages exceeding leftover limit' => sub { + my $req = Mojo::Message::Request->new; + is $req->content->max_leftover_size, 262144, 'right size'; + $req->parse("GET /one HTTP/1.1\x0d\x0a"); + $req->parse("Content-Length: 120000\x0d\x0a\x0d\x0a@{['a' x 120000]}"); + ok $req->is_finished, 'request is finished'; + is $req->content->leftovers, '', 'no leftovers'; + $req->parse("GET /two HTTP/1.1\x0d\x0a"); + $req->parse("Content-Length: 120000\x0d\x0a\x0d\x0a@{['b' x 120000]}"); + is length($req->content->leftovers), 120045, 'right size'; + $req->parse("GET /three HTTP/1.1\x0d\x0a"); + $req->parse("Content-Length: 120000\x0d\x0a\x0d\x0a@{['c' x 120000]}"); + is length($req->content->leftovers), 240092, 'right size'; + $req->parse("GET /four HTTP/1.1\x0d\x0a"); + $req->parse("Content-Length: 120000\x0d\x0a\x0d\x0a@{['d' x 120000]}"); + is length($req->content->leftovers), 360138, 'right size'; + $req->parse("GET /five HTTP/1.1\x0d\x0a"); + $req->parse("Content-Length: 120000\x0d\x0a\x0d\x0a@{['e' x 120000]}"); + is length($req->content->leftovers), 360138, 'right size'; + is $req->error, undef, 'no error'; + is $req->method, 'GET', 'right method'; + is $req->version, '1.1', 'right version'; + is $req->url, '/one', 'right URL'; + is $req->body, 'a' x 120000, 'right content'; +}; + +subtest 'Parse pipelined HTTP 1.1 messages exceeding leftover limit (chunked)' => sub { + my $req = Mojo::Message::Request->new; + $req->parse("GET /one HTTP/1.1\x0d\x0a"); + $req->parse("Transfer-Encoding: chunked\x0d\x0a\x0d\x0a"); + $req->parse("ea60\x0d\x0a"); + $req->parse('a' x 60000); + $req->parse("\x0d\x0aea60\x0d\x0a"); + $req->parse('a' x 60000); + $req->parse("\x0d\x0a0\x0d\x0a\x0d\x0a"); + ok $req->is_finished, 'request is finished'; + is $req->content->leftovers, '', 'no leftovers'; + $req->parse("GET /two HTTP/1.1\x0d\x0a"); + $req->parse("Content-Length: 120000\x0d\x0a\x0d\x0a@{['b' x 120000]}"); + is length($req->content->leftovers), 120045, 'right size'; + $req->parse("GET /three HTTP/1.1\x0d\x0a"); + $req->parse("Content-Length: 120000\x0d\x0a\x0d\x0a@{['c' x 120000]}"); + is length($req->content->leftovers), 240092, 'right size'; + $req->parse("GET /four HTTP/1.1\x0d\x0a"); + $req->parse("Content-Length: 120000\x0d\x0a\x0d\x0a@{['d' x 120000]}"); + is length($req->content->leftovers), 360138, 'right size'; + $req->parse("GET /five HTTP/1.1\x0d\x0a"); + $req->parse("Content-Length: 120000\x0d\x0a\x0d\x0a@{['e' x 120000]}"); + is length($req->content->leftovers), 360138, 'right size'; + is $req->error, undef, 'no error'; + is $req->method, 'GET', 'right method'; + is $req->version, '1.1', 'right version'; + is $req->url, '/one', 'right URL'; + is $req->body, 'a' x 120000, 'right content'; +}; + +subtest 'Parse HTTP 1.1 start-line, no headers and body' => sub { + my $req = Mojo::Message::Request->new; + $req->parse("GET / HTTP/1.1\x0d\x0a\x0d\x0a"); + ok $req->is_finished, 'request is finished'; + is $req->method, 'GET', 'right method'; + is $req->version, '1.1', 'right version'; + is $req->url, '/', 'right URL'; +}; + +subtest 'Parse HTTP 1.1 start-line, no headers and body (small chunks)' => sub { + my $req = Mojo::Message::Request->new; + $req->parse('G'); + ok !$req->is_finished, 'request is not finished'; + $req->parse('E'); + ok !$req->is_finished, 'request is not finished'; + $req->parse('T'); + ok !$req->is_finished, 'request is not finished'; + $req->parse(' '); + ok !$req->is_finished, 'request is not finished'; + $req->parse('/'); + ok !$req->is_finished, 'request is not finished'; + $req->parse(' '); + ok !$req->is_finished, 'request is not finished'; + $req->parse('H'); + ok !$req->is_finished, 'request is not finished'; + $req->parse('T'); + ok !$req->is_finished, 'request is not finished'; + $req->parse('T'); + ok !$req->is_finished, 'request is not finished'; + $req->parse('P'); + ok !$req->is_finished, 'request is not finished'; + $req->parse('/'); + ok !$req->is_finished, 'request is not finished'; + $req->parse('1'); + ok !$req->is_finished, 'request is not finished'; + $req->parse('.'); + ok !$req->is_finished, 'request is not finished'; + $req->parse('1'); + ok !$req->is_finished, 'request is not finished'; + $req->parse("\x0d"); + ok !$req->is_finished, 'request is not finished'; + $req->parse("\x0a"); + ok !$req->is_finished, 'request is not finished'; + $req->parse("\x0d"); + ok !$req->is_finished, 'request is not finished'; + $req->parse("\x0a"); + ok $req->is_finished, 'request is finished'; + is $req->method, 'GET', 'right method'; + is $req->version, '1.1', 'right version'; + is $req->url, '/', 'right URL'; +}; + +subtest 'Parse pipelined HTTP 1.1 start-line, no headers and body' => sub { + my $req = Mojo::Message::Request->new; + $req->parse("GET / HTTP/1.1\x0d\x0a\x0d\x0aGET / HTTP/1.1\x0d\x0a\x0d\x0a"); + ok $req->is_finished, 'request is finished'; + is $req->content->leftovers, "GET / HTTP/1.1\x0d\x0a\x0d\x0a", 'second request in leftovers'; +}; + +subtest 'Parse HTTP 1.1 start-line, no headers and body with leading CRLFs' => sub { + my $req = Mojo::Message::Request->new; + $req->parse("\x0d\x0a GET / HTTP/1.1\x0d\x0a\x0d\x0a"); + ok $req->is_finished, 'request is finished'; + is $req->method, 'GET', 'right method'; + is $req->version, '1.1', 'right version'; + is $req->url, '/', 'right URL'; +}; + +subtest 'Parse WebSocket handshake request' => sub { + my $req = Mojo::Message::Request->new; + $req->parse("GET /demo HTTP/1.1\x0d\x0a"); + $req->parse("Host: example.com\x0d\x0a"); + $req->parse("Connection: Upgrade\x0d\x0a"); + $req->parse("Sec-WebSocket-Key: abcdef=\x0d\x0a"); + $req->parse("Sec-WebSocket-Protocol: sample\x0d\x0a"); + $req->parse("Upgrade: websocket\x0d\x0a\x0d\x0a"); + ok $req->is_finished, 'request is finished'; + ok $req->is_handshake, 'request is WebSocket handshake'; + is $req->method, 'GET', 'right method'; + is $req->version, '1.1', 'right version'; + is $req->url, '/demo', 'right URL'; + is $req->headers->host, 'example.com', 'right "Host" value'; + is $req->headers->connection, 'Upgrade', 'right "Connection" value'; + is $req->headers->sec_websocket_protocol, 'sample', 'right "Sec-WebSocket-Protocol" value'; + is $req->headers->upgrade, 'websocket', 'right "Upgrade" value'; + is $req->headers->sec_websocket_key, 'abcdef=', 'right "Sec-WebSocket-Key" value'; + is $req->body, '', 'no content'; +}; + +subtest 'Parse HTTP 1.0 start-line and headers, no body' => sub { + my $req = Mojo::Message::Request->new; + $req->parse("GET /foo/bar/baz.html HTTP/1.0\x0d\x0a"); + $req->parse("Content-Type: text/plain;charset=UTF-8\x0d\x0a"); + $req->parse("Content-Length: 0\x0d\x0a\x0d\x0a"); + ok $req->is_finished, 'request is finished'; + ok !$req->is_handshake, 'request is not a WebSocket handshake'; + is $req->method, 'GET', 'right method'; + is $req->version, '1.0', 'right version'; + is $req->url, '/foo/bar/baz.html', 'right URL'; + is $req->headers->content_type, 'text/plain;charset=UTF-8', 'right "Content-Type" value'; + is $req->headers->content_length, 0, 'right "Content-Length" value'; + is $req->content->charset, 'UTF-8', 'right charset'; +}; + +subtest 'Parse HTTP 1.0 start-line and headers, no body (missing Content-Length)' => sub { + my $req = Mojo::Message::Request->new; + $req->parse("GET /foo/bar/baz.html HTTP/1.0\x0d\x0a"); + $req->parse("Content-Type: text/plain\x0d\x0a\x0d\x0a"); + ok $req->is_finished, 'request is finished'; + is $req->method, 'GET', 'right method'; + is $req->version, '1.0', 'right version'; + is $req->url, '/foo/bar/baz.html', 'right URL'; + is $req->headers->content_type, 'text/plain', 'right "Content-Type" value'; + is $req->headers->content_length, undef, 'no "Content-Length" value'; +}; + +subtest 'Parse full HTTP 1.0 request (file storage)' => sub { local $ENV{MOJO_MAX_MEMORY_SIZE} = 12; - $req = Mojo::Message::Request->new; + my $req = Mojo::Message::Request->new; my ($upgrade, $size); $req->content->asset->on( upgrade => sub { @@ -331,24 +347,24 @@ is $req->headers->content_length, undef, 'no "Content-Length" value'; is $req->url, '/foo/bar/baz.html?foo=13', 'right URL'; is $req->headers->content_type, 'text/plain', 'right "Content-Type" value'; is $req->headers->content_length, 27, 'right "Content-Length" value'; -} - -# Parse HTTP 1.0 start-line and headers, no body (missing Content-Length) -$req = Mojo::Message::Request->new; -$req->parse("GET /foo/bar/baz.html HTTP/1.0\x0d\x0a"); -$req->parse("Content-Type: text/plain\x0d\x0a"); -$req->parse("Connection: Close\x0d\x0a\x0d\x0a"); -ok $req->is_finished, 'request is finished'; -is $req->method, 'GET', 'right method'; -is $req->version, '1.0', 'right version'; -is $req->url, '/foo/bar/baz.html', 'right URL'; -is $req->headers->content_type, 'text/plain', 'right "Content-Type" value'; -is $req->headers->content_length, undef, 'no "Content-Length" value'; - -# Parse HTTP 1.0 start-line (with line size limit) -{ +}; + +subtest 'Parse HTTP 1.0 start-line and headers, no body (missing Content-Length)' => sub { + my $req = Mojo::Message::Request->new; + $req->parse("GET /foo/bar/baz.html HTTP/1.0\x0d\x0a"); + $req->parse("Content-Type: text/plain\x0d\x0a"); + $req->parse("Connection: Close\x0d\x0a\x0d\x0a"); + ok $req->is_finished, 'request is finished'; + is $req->method, 'GET', 'right method'; + is $req->version, '1.0', 'right version'; + is $req->url, '/foo/bar/baz.html', 'right URL'; + is $req->headers->content_type, 'text/plain', 'right "Content-Type" value'; + is $req->headers->content_length, undef, 'no "Content-Length" value'; +}; + +subtest 'Parse HTTP 1.0 start-line (with line size limit)' => sub { local $ENV{MOJO_MAX_LINE_SIZE} = 5; - $req = Mojo::Message::Request->new; + my $req = Mojo::Message::Request->new; my $limit; $req->on(finish => sub { $limit = shift->is_limit_exceeded }); ok !$req->is_limit_exceeded, 'limit is not exceeded'; @@ -361,24 +377,22 @@ is $req->headers->content_length, undef, 'no "Content-Length" value'; $req->error({message => 'Nothing important.'}); is $req->error->{message}, 'Nothing important.', 'right error'; ok $req->is_limit_exceeded, 'limit is still exceeded'; -} +}; -# Parse HTTP 1.0 start-line and headers (with line size limit) -{ +subtest 'Parse HTTP 1.0 start-line and headers (with line size limit)' => sub { local $ENV{MOJO_MAX_LINE_SIZE} = 20; - $req = Mojo::Message::Request->new; + my $req = Mojo::Message::Request->new; is $req->max_line_size, 20, 'right size'; $req->parse("GET / HTTP/1.0\x0d\x0a"); $req->parse("Content-Type: text/plain\x0d\x0a"); ok $req->is_finished, 'request is finished'; is $req->error->{message}, 'Maximum header size exceeded', 'right error'; ok $req->is_limit_exceeded, 'limit is exceeded'; -} +}; -# Parse HTTP 1.0 start-line (with message size limit) -{ +subtest 'Parse HTTP 1.0 start-line (with message size limit)' => sub { local $ENV{MOJO_MAX_MESSAGE_SIZE} = 5; - $req = Mojo::Message::Request->new; + my $req = Mojo::Message::Request->new; my $limit; $req->on(finish => sub { $limit = shift->is_limit_exceeded }); is $req->max_message_size, 5, 'right size'; @@ -387,23 +401,21 @@ is $req->headers->content_length, undef, 'no "Content-Length" value'; is $req->error->{message}, 'Maximum message size exceeded', 'right error'; ok $req->is_limit_exceeded, 'limit is exceeded'; ok $limit, 'limit is exceeded'; -} +}; -# Parse HTTP 1.0 start-line and headers (with message size limit) -{ +subtest 'Parse HTTP 1.0 start-line and headers (with message size limit)' => sub { local $ENV{MOJO_MAX_MESSAGE_SIZE} = 20; - $req = Mojo::Message::Request->new; + my $req = Mojo::Message::Request->new; $req->parse("GET / HTTP/1.0\x0d\x0a"); $req->parse("Content-Type: text/plain\x0d\x0a"); ok $req->is_finished, 'request is finished'; is $req->error->{message}, 'Maximum message size exceeded', 'right error'; ok $req->is_limit_exceeded, 'limit is exceeded'; -} +}; -# Parse HTTP 1.0 start-line, headers and body (with message size limit) -{ +subtest 'Parse HTTP 1.0 start-line, headers and body (with message size limit)' => sub { local $ENV{MOJO_MAX_MESSAGE_SIZE} = 50; - $req = Mojo::Message::Request->new; + my $req = Mojo::Message::Request->new; $req->parse("GET / HTTP/1.0\x0d\x0a"); $req->parse("Content-Length: 24\x0d\x0a\x0d\x0a"); $req->parse('Hello World!'); @@ -411,12 +423,11 @@ is $req->headers->content_length, undef, 'no "Content-Length" value'; ok $req->is_finished, 'request is finished'; is $req->error->{message}, 'Maximum message size exceeded', 'right error'; ok $req->is_limit_exceeded, 'limit is exceeded'; -} +}; -# Parse HTTP 1.1 message with headers exceeding line limit -{ +subtest 'Parse HTTP 1.1 message with headers exceeding line limit' => sub { local $ENV{MOJO_MAX_LINES} = 5; - $req = Mojo::Message::Request->new; + my $req = Mojo::Message::Request->new; is $req->headers->max_lines, 5, 'right number'; $req->parse("GET / HTTP/1.1\x0d\x0a"); $req->parse("A: a\x0d\x0aB: b\x0d\x0aC: c\x0d\x0aD: d\x0d\x0a"); @@ -428,1623 +439,1690 @@ is $req->headers->content_length, undef, 'no "Content-Length" value'; is $req->method, 'GET', 'right method'; is $req->version, '1.1', 'right version'; is $req->url, '/', 'right URL'; -} - -# Parse full HTTP 1.0 request (solitary LF) -$req = Mojo::Message::Request->new; -my $body = ''; -$req->content->on(read => sub { $body .= pop }); -$req->parse('GET /foo/bar/baz.html?fo'); -$req->parse("o=13 HTTP/1.0\x0aContent"); -$req->parse('-Type: text/'); -$req->parse("plain\x0aContent-Length: 27\x0a\x0aH"); -$req->parse("ello World!\n1234\nlalalala\n"); -ok $req->is_finished, 'request is finished'; -is $req->method, 'GET', 'right method'; -is $req->version, '1.0', 'right version'; -is $req->url, '/foo/bar/baz.html?foo=13', 'right URL'; -is $req->headers->content_type, 'text/plain', 'right "Content-Type" value'; -is $req->headers->content_length, 27, 'right "Content-Length" value'; -is $req->body, "Hello World!\n1234\nlalalala\n", 'right content'; -is $body, "Hello World!\n1234\nlalalala\n", 'right content'; - -# Parse full HTTP 1.0 request (no scheme and empty elements in path) -$req = Mojo::Message::Request->new; -$req->parse('GET //foo/bar//baz.html?fo'); -$req->parse("o=13 HTTP/1.0\x0d\x0aContent"); -$req->parse('-Type: text/'); -$req->parse("plain\x0d\x0aContent-Length: 27\x0d\x0a\x0d\x0aHell"); -$req->parse("o World!\n1234\nlalalala\n"); -ok $req->is_finished, 'request is finished'; -is $req->method, 'GET', 'right method'; -is $req->version, '1.0', 'right version'; -is $req->url->host, undef, 'no host'; -is $req->url->path, '//foo/bar//baz.html', 'right path'; -is $req->url, '//foo/bar//baz.html?foo=13', 'right URL'; -is $req->headers->content_type, 'text/plain', 'right "Content-Type" value'; -is $req->headers->content_length, 27, 'right "Content-Length" value'; - -# Parse full HTTP 1.0 request (behind reverse proxy) -$req = Mojo::Message::Request->new; -$req->parse('GET /foo/bar/baz.html?fo'); -$req->parse("o=13 HTTP/1.0\x0d\x0aContent"); -$req->parse('-Type: text/'); -$req->parse("plain\x0d\x0aContent-Length: 27\x0d\x0a"); -$req->parse("Host: mojolicious.org\x0d\x0a"); -$req->parse("X-Forwarded-For: 192.168.2.1, 127.0.0.1\x0d\x0a\x0d\x0a"); -$req->parse("Hello World!\n1234\nlalalala\n"); -ok $req->is_finished, 'request is finished'; -is $req->method, 'GET', 'right method'; -is $req->version, '1.0', 'right version'; -is $req->url, '/foo/bar/baz.html?foo=13', 'right URL'; -is $req->url->to_abs, 'http://mojolicious.org/foo/bar/baz.html?foo=13', 'right absolute URL'; -is $req->headers->content_type, 'text/plain', 'right "Content-Type" value'; -is $req->headers->content_length, 27, 'right "Content-Length" value'; - -# Parse full HTTP 1.0 request with zero chunk -$req = Mojo::Message::Request->new; -$finished = undef; -$req->on(finish => sub { $finished = shift->is_finished }); -$req->parse('GET /foo/bar/baz.html?fo'); -$req->parse("o=13 HTTP/1.0\x0d\x0aContent"); -$req->parse('-Type: text/'); -$req->parse("plain\x0d\x0aContent-Length: 27\x0d\x0a\x0d\x0aHell"); -$req->parse("o World!\n123"); -$req->parse('0'); -$req->parse("\nlalalala\n"); -ok $finished, 'finish event has been emitted'; -ok $req->is_finished, 'request is finished'; -is $req->method, 'GET', 'right method'; -is $req->version, '1.0', 'right version'; -is $req->url, '/foo/bar/baz.html?foo=13', 'right URL'; -is $req->headers->content_type, 'text/plain', 'right "Content-Type" value'; -is $req->headers->content_length, 27, 'right "Content-Length" value'; - -# Parse full HTTP 1.0 request with UTF-8 form input -$req = Mojo::Message::Request->new; -$req->parse('GET /foo/bar/baz.html?fo'); -$req->parse("o=13 HTTP/1.0\x0d\x0aContent"); -$req->parse('-Type: application/'); -$req->parse("x-www-form-urlencoded\x0d\x0aContent-Length: 14"); -$req->parse("\x0d\x0a\x0d\x0a"); -$req->parse('name=%E2%98%83'); -ok $req->is_finished, 'request is finished'; -is $req->method, 'GET', 'right method'; -is $req->version, '1.0', 'right version'; -is $req->url, '/foo/bar/baz.html?foo=13', 'right URL'; -is $req->headers->content_type, 'application/x-www-form-urlencoded', 'right "Content-Type" value'; -is $req->headers->content_length, 14, 'right "Content-Length" value'; -is $req->param('name'), '☃', 'right value'; - -# Parse HTTP 1.1 gzip compressed request (no decompression) -my $compressed = gzip my $uncompressed = 'abc' x 1000; -$req = Mojo::Message::Request->new; -$req->parse("POST /foo HTTP/1.1\x0d\x0a"); -$req->parse("Content-Type: text/plain\x0d\x0a"); -$req->parse("Content-Length: @{[length $compressed]}\x0d\x0a"); -$req->parse("Content-Encoding: GZip\x0d\x0a\x0d\x0a"); -ok $req->content->is_compressed, 'content is compressed'; -$req->parse($compressed); -ok $req->content->is_compressed, 'content is still compressed'; -ok $req->is_finished, 'request is finished'; -is $req->method, 'POST', 'right method'; -is $req->version, '1.1', 'right version'; -is $req->url, '/foo', 'right URL'; -is $req->headers->content_type, 'text/plain', 'right "Content-Type" value'; -is $req->headers->content_length, length($compressed), 'right "Content-Length" value'; -is $req->body, $compressed, 'right content'; - -# Parse HTTP 1.1 chunked request -$req = Mojo::Message::Request->new; -is $req->content->progress, 0, 'right progress'; -$req->parse("POST /foo/bar/baz.html?foo=13 HTTP/1.1\x0d\x0a"); -is $req->content->progress, 0, 'right progress'; -$req->parse("Content-Type: text/plain\x0d\x0a"); -$req->parse("Transfer-Encoding: chunked\x0d\x0a\x0d\x0a"); -is $req->content->progress, 0, 'right progress'; -$req->parse("4\x0d\x0a"); -is $req->content->progress, 3, 'right progress'; -$req->parse("abcd\x0d\x0a"); -is $req->content->progress, 9, 'right progress'; -$req->parse("9\x0d\x0a"); -is $req->content->progress, 12, 'right progress'; -$req->parse("abcdefghi\x0d\x0a"); -is $req->content->progress, 23, 'right progress'; -$req->parse("0\x0d\x0a\x0d\x0a"); -is $req->content->progress, 28, 'right progress'; -ok $req->is_finished, 'request is finished'; -is $req->method, 'POST', 'right method'; -is $req->version, '1.1', 'right version'; -is $req->url, '/foo/bar/baz.html?foo=13', 'right URL'; -is $req->headers->content_length, 13, 'right "Content-Length" value'; -is $req->headers->content_type, 'text/plain', 'right "Content-Type" value'; -is $req->content->asset->size, 13, 'right size'; -is $req->content->asset->slurp, 'abcdabcdefghi', 'right content'; - -# Parse HTTP 1.1 chunked request with callbacks -$req = Mojo::Message::Request->new; -my $progress = my $buffer = my $finish = ''; -$req->on( - progress => sub { - my $self = shift; - $progress ||= $self->url->path if $self->content->is_parsing_body; - } -); -$req->content->unsubscribe('read')->on(read => sub { $buffer .= pop }); -$req->on(finish => sub { $finish .= shift->url->query }); -$req->parse("POST /foo/bar/baz.html?foo=13 HTTP/1.1\x0d\x0a"); -is $progress, '', 'no progress'; -$req->parse("Content-Type: text/plain\x0d\x0a"); -is $progress, '', 'no progress'; -$req->parse("Transfer-Encoding: chunked\x0d\x0a\x0d\x0a"); -is $progress, '/foo/bar/baz.html', 'made progress'; -$req->parse("4\x0d\x0a"); -$req->parse("abcd\x0d\x0a"); -$req->parse("9\x0d\x0a"); -$req->parse("abcdefghi\x0d\x0a"); -is $finish, '', 'not finished yet'; -$req->parse("0\x0d\x0a\x0d\x0a"); -is $finish, 'foo=13', 'finished'; -is $progress, '/foo/bar/baz.html', 'made progress'; -ok $req->is_finished, 'request is finished'; -is $req->method, 'POST', 'right method'; -is $req->version, '1.1', 'right version'; -is $req->url, '/foo/bar/baz.html?foo=13', 'right URL'; -is $req->headers->content_length, 13, 'right "Content-Length" value'; -is $req->headers->content_type, 'text/plain', 'right "Content-Type" value'; -is $buffer, 'abcdabcdefghi', 'right content'; - -# Parse HTTP 1.1 "application/x-www-form-urlencoded" -$req = Mojo::Message::Request->new; -$req->parse("POST /foo/bar/baz.html?foo=13 HTTP/1.1\x0d\x0a"); -$req->parse("Content-Length: 25\x0d\x0a"); -$req->parse("Content-Type: application/x-www-form-urlencoded\x0d\x0a"); -$req->parse("\x0d\x0afoo=bar&+tset=23+&foo=bar"); -ok $req->is_finished, 'request is finished'; -is $req->method, 'POST', 'right method'; -is $req->version, '1.1', 'right version'; -is $req->url, '/foo/bar/baz.html?foo=13', 'right URL'; -is $req->headers->content_type, 'application/x-www-form-urlencoded', 'right "Content-Type" value'; -is $req->content->asset->size, 25, 'right size'; -is $req->content->asset->slurp, 'foo=bar&+tset=23+&foo=bar', 'right content'; -is_deeply $req->body_params->to_hash->{foo}, [qw(bar bar)], 'right values'; -is $req->body_params->to_hash->{' tset'}, '23 ', 'right value'; -is $req->body_params, 'foo=bar&+tset=23+&foo=bar', 'right parameters'; -is_deeply $req->params->to_hash->{foo}, [qw(bar bar 13)], 'right values'; -is_deeply $req->every_param('foo'), [qw(bar bar 13)], 'right values'; -is $req->param(' tset'), '23 ', 'right value'; -$req->param('set', 'single'); -is $req->param('set'), 'single', 'setting single param works'; -$req->param('multi', 1, 2, 3); -is_deeply $req->every_param('multi'), [qw(1 2 3)], 'setting multiple value param works'; -is $req->param('test23'), undef, 'no value'; - -# Parse HTTP 1.1 chunked request with trailing headers -$req = Mojo::Message::Request->new; -$req->parse("POST /foo/bar/baz.html?foo=13&bar=23 HTTP/1.1\x0d\x0a"); -$req->parse("Content-Type: text/plain\x0d\x0a"); -$req->parse("Transfer-Encoding: whatever\x0d\x0a"); -$req->parse("Trailer: X-Trailer1; X-Trailer2\x0d\x0a\x0d\x0a"); -$req->parse("4\x0d\x0a"); -$req->parse("abcd\x0d\x0a"); -$req->parse("9\x0d\x0a"); -$req->parse("abcdefghi\x0d\x0a"); -$req->parse("0\x0d\x0a"); -$req->parse("X-Trailer1: test\x0d\x0a"); -$req->parse("X-Trailer2: 123\x0d\x0a\x0d\x0a"); -ok $req->is_finished, 'request is finished'; -is $req->method, 'POST', 'right method'; -is $req->version, '1.1', 'right version'; -is $req->url, '/foo/bar/baz.html?foo=13&bar=23', 'right URL'; -is $req->query_params, 'foo=13&bar=23', 'right parameters'; -is $req->headers->content_type, 'text/plain', 'right "Content-Type" value'; -is $req->headers->header('X-Trailer1'), 'test', 'right "X-Trailer1" value'; -is $req->headers->header('X-Trailer2'), '123', 'right "X-Trailer2" value'; -is $req->headers->content_length, 13, 'right "Content-Length" value'; -is $req->content->asset->size, 13, 'right size'; -is $req->content->asset->slurp, 'abcdabcdefghi', 'right content'; - -# Parse HTTP 1.1 chunked request with trailing headers (different variation) -$req = Mojo::Message::Request->new; -$req->parse("POST /foo/bar/baz.html?foo=13&bar=23 HTTP/1.1\x0d\x0a"); -$req->parse("Content-Type: text/plain\x0d\x0aTransfer-Enc"); -$req->parse("oding: chunked\x0d\x0a"); -$req->parse("Trailer: X-Trailer\x0d\x0a\x0d\x0a"); -$req->parse("4\x0d\x0a"); -$req->parse("abcd\x0d\x0a"); -$req->parse("9\x0d\x0a"); -$req->parse("abcdefghi\x0d\x0a"); -$req->parse("0\x0d\x0aX-Trailer: 777\x0d\x0a\x0d\x0aLEFTOVER"); -ok $req->is_finished, 'request is finished'; -is $req->method, 'POST', 'right method'; -is $req->version, '1.1', 'right version'; -is $req->url, '/foo/bar/baz.html?foo=13&bar=23', 'right URL'; -is $req->query_params, 'foo=13&bar=23', 'right parameters'; -ok !defined $req->headers->transfer_encoding, 'no "Transfer-Encoding" value'; -is $req->headers->content_type, 'text/plain', 'right "Content-Type" value'; -is $req->headers->header('X-Trailer'), '777', 'right "X-Trailer" value'; -is $req->headers->content_length, 13, 'right "Content-Length" value'; -is $req->content->asset->size, 13, 'right size'; -is $req->content->asset->slurp, 'abcdabcdefghi', 'right content'; - -# Parse HTTP 1.1 chunked request with trailing headers (different variation) -$req = Mojo::Message::Request->new; -$req->parse("POST /foo/bar/baz.html?foo=13&bar=23 HTTP/1.1\x0d\x0a"); -$req->parse("Content-Type: text/plain\x0d\x0a"); -$req->parse("Transfer-Encoding: chunked\x0d\x0a"); -$req->parse("Trailer: X-Trailer1; X-Trailer2\x0d\x0a\x0d\x0a"); -$req->parse("4\x0d\x0a"); -$req->parse("abcd\x0d\x0a"); -$req->parse("9\x0d\x0a"); -$req->parse("abcdefghi\x0d\x0a"); -$req->parse("0\x0d\x0aX-Trailer1: test\x0d\x0aX-Trailer2: 123\x0d\x0a\x0d\x0a"); -ok $req->is_finished, 'request is finished'; -is $req->method, 'POST', 'right method'; -is $req->version, '1.1', 'right version'; -is $req->url, '/foo/bar/baz.html?foo=13&bar=23', 'right URL'; -is $req->query_params, 'foo=13&bar=23', 'right parameters'; -is $req->headers->content_type, 'text/plain', 'right "Content-Type" value'; -is $req->headers->header('X-Trailer1'), 'test', 'right "X-Trailer1" value'; -is $req->headers->header('X-Trailer2'), '123', 'right "X-Trailer2" value'; -is $req->headers->content_length, 13, 'right "Content-Length" value'; -is $req->content->asset->size, 13, 'right size'; -is $req->content->asset->slurp, 'abcdabcdefghi', 'right content'; - -# Parse HTTP 1.1 chunked request with trailing headers (no Trailer header) -$req = Mojo::Message::Request->new; -$req->parse("POST /foo/bar/baz.html?foo=13&bar=23 HTTP/1.1\x0d\x0a"); -$req->parse("Content-Type: text/plain\x0d\x0a"); -$req->parse("Transfer-Encoding: chunked\x0d\x0a\x0d\x0a"); -$req->parse("4\x0d\x0a"); -$req->parse("abcd\x0d\x0a"); -$req->parse("9\x0d\x0a"); -$req->parse("abcdefghi\x0d\x0a"); -$req->parse("0\x0d\x0aX-Trailer1: test\x0d\x0aX-Trailer2: 123\x0d\x0a\x0d\x0a"); -ok $req->is_finished, 'request is finished'; -is $req->method, 'POST', 'right method'; -is $req->version, '1.1', 'right version'; -is $req->url, '/foo/bar/baz.html?foo=13&bar=23', 'right URL'; -is $req->query_params, 'foo=13&bar=23', 'right parameters'; -is $req->headers->content_type, 'text/plain', 'right "Content-Type" value'; -is $req->headers->header('X-Trailer1'), 'test', 'right "X-Trailer1" value'; -is $req->headers->header('X-Trailer2'), '123', 'right "X-Trailer2" value'; -is $req->headers->content_length, 13, 'right "Content-Length" value'; -is $req->content->asset->size, 13, 'right size'; -is $req->content->asset->slurp, 'abcdabcdefghi', 'right content'; - -# Parse HTTP 1.1 multipart request -$req = Mojo::Message::Request->new; -is $req->content->progress, 0, 'right progress'; -$req->parse("GET /foo/bar/baz.html?foo13 HTTP/1.1\x0d\x0a"); -is $req->content->progress, 0, 'right progress'; -$req->parse("Content-Length: 416\x0d\x0a"); -$req->parse('Content-Type: multipart/form-data; bo'); -is $req->content->progress, 0, 'right progress'; -$req->parse("undary=----------0xKhTmLbOuNdArY\x0d\x0a\x0d\x0a"); -is $req->content->progress, 0, 'right progress'; -$req->parse("\x0d\x0a------------0xKhTmLbOuNdArY\x0d\x0a"); -is $req->content->progress, 31, 'right progress'; -$req->parse("Content-Disposition: form-data; name=\"text1\"\x0d\x0a"); -$req->parse("\x0d\x0ahallo welt test123\n"); -$req->parse("\x0d\x0a------------0xKhTmLbOuNdArY\x0d\x0a"); -$req->parse("Content-Disposition: form-data; name=\"text2\"\x0d\x0a"); -$req->parse("\x0d\x0a\x0d\x0a------------0xKhTmLbOuNdArY\x0d\x0a"); -$req->parse('Content-Disposition: form-data;name="upload";file'); -$req->parse("name=\"hello.pl\"\x0d\x0a"); -$req->parse("Content-Type: application/octet-stream\x0d\x0a\x0d\x0a"); -$req->parse("#!/usr/bin/perl\n\n"); -$req->parse("use strict;\n"); -$req->parse("use warnings;\n\n"); -$req->parse("print \"Hello World :)\\n\"\n"); -$req->parse("\x0d\x0a------------0xKhTmLbOuNdArY--"); -is $req->content->progress, 416, 'right progress'; -ok $req->is_finished, 'request is finished'; -ok $req->content->is_multipart, 'multipart content'; -is $req->body, '', 'no content'; -is $req->method, 'GET', 'right method'; -is $req->version, '1.1', 'right version'; -is $req->url, '/foo/bar/baz.html?foo13', 'right URL'; -is $req->query_params, 'foo13', 'right parameters'; -is $req->headers->content_type, 'multipart/form-data; boundary=----------0xKhTmLbOuNdArY', 'right "Content-Type" value'; -is $req->headers->content_length, 416, 'right "Content-Length" value'; -ok !$req->content->parts->[0]->is_multipart, 'no multipart content'; -ok !$req->content->parts->[1]->is_multipart, 'no multipart content'; -ok !$req->content->parts->[2]->is_multipart, 'no multipart content'; -ok !$req->content->parts->[0]->asset->is_file, 'stored in memory'; -is $req->content->parts->[0]->asset->slurp, "hallo welt test123\n", 'right content'; -is $req->body_params->to_hash->{text1}, "hallo welt test123\n", 'right value'; -is $req->body_params->to_hash->{text2}, '', 'right value'; -is $req->upload('upload')->filename, 'hello.pl', 'right filename'; -ok !$req->upload('upload')->asset->is_file, 'stored in memory'; -is $req->upload('upload')->asset->size, 69, 'right size'; -my $tempdir = tempdir; -my $file = $tempdir->child('MOJO_TMP.' . time . '.txt'); -is $req->upload('upload')->move_to($file)->filename, 'hello.pl', 'right filename'; -ok unlink($file), 'unlinked file'; -is $req->content->boundary, '----------0xKhTmLbOuNdArY', 'right boundary'; - -# Parse HTTP 1.1 multipart request (too big for memory) -$req = Mojo::Message::Request->new; -$req->content->on( - body => sub { - my $single = shift; - $single->on( - upgrade => sub { - my ($single, $multi) = @_; - $multi->on( - part => sub { - my ($multi, $part) = @_; - $part->asset->max_memory_size(5); - } - ); - } - ); - } -); -$req->parse("GET /foo/bar/baz.html?foo13 HTTP/1.1\x0d\x0a"); -$req->parse("Content-Length: 562\x0d\x0a"); -$req->parse('Content-Type: multipart/form-data; bo'); -$req->parse("undary=----------0xKhTmLbOuNdArY\x0d\x0a\x0d\x0a"); -$req->parse("\x0d\x0a------------0xKhTmLbOuNdArY\x0d\x0a"); -$req->parse("Content-Disposition: form-data; name=\"text1\"\x0d\x0a"); -$req->parse("\x0d\x0ahallo welt test123\n"); -$req->parse("\x0d\x0a------------0xKhTmLbOuNdArY\x0d\x0a"); -$req->parse("Content-Disposition: form-data; name=\"text2\"\x0d\x0a"); -$req->parse("\x0d\x0a\x0d\x0a------------0xKhTmLbOuNdArY\x0d\x0a"); -$req->parse('Content-Disposition: form-data; name="upload"; file'); -$req->parse("name=\"hello.pl\"\x0d\x0a"); -$req->parse("Content-Type: application/octet-stream\x0d\x0a\x0d\x0a"); -$req->parse("#!/usr/bin/perl\n\n"); -$req->parse("use strict;\n"); -$req->parse("use warnings;\n\n"); -$req->parse("print \"Hello World :)\\n\"\n"); -$req->parse("\x0d\x0a------------0xKhTmLbOuNdArY\x0d\x0a"); -$req->parse('Content-Disposition: form-data; name="upload"; filename'); -$req->parse("=\"bye.txt\"\x0d\x0a"); -$req->parse("Content-Type: application/octet-stream\x0d\x0a\x0d\x0a"); -$req->parse("Bye!\x0d\x0a------------0xKhTmLbOuNdArY--"); -ok $req->is_finished, 'request is finished'; -ok $req->content->is_multipart, 'multipart content'; -is $req->body, '', 'no content'; -is $req->method, 'GET', 'right method'; -is $req->version, '1.1', 'right version'; -is $req->url, '/foo/bar/baz.html?foo13', 'right URL'; -is $req->query_params, 'foo13', 'right parameters'; -is $req->headers->content_type, 'multipart/form-data; boundary=----------0xKhTmLbOuNdArY', 'right "Content-Type" value'; -is $req->headers->content_length, 562, 'right "Content-Length" value'; -ok !$req->content->parts->[0]->is_multipart, 'no multipart content'; -ok !$req->content->parts->[1]->is_multipart, 'no multipart content'; -ok !$req->content->parts->[2]->is_multipart, 'no multipart content'; -ok $req->content->parts->[0]->asset->is_file, 'stored in file'; -is $req->content->parts->[0]->asset->slurp, "hallo welt test123\n", 'right content'; -is $req->body_params->to_hash->{text1}, "hallo welt test123\n", 'right value'; -is $req->body_params->to_hash->{text2}, '', 'right value'; -is $req->upload('upload')->filename, 'bye.txt', 'right filename'; -is $req->upload('upload')->asset->size, 4, 'right size'; -is $req->every_upload('upload')->[0]->filename, 'hello.pl', 'right filename'; -ok $req->every_upload('upload')->[0]->asset->is_file, 'stored in file'; -is $req->every_upload('upload')->[0]->asset->size, 69, 'right size'; -is $req->every_upload('upload')->[1]->filename, 'bye.txt', 'right filename'; -ok !$req->every_upload('upload')->[1]->asset->is_file, 'stored in memory'; -is $req->every_upload('upload')->[1]->asset->size, 4, 'right size'; - -# Parse HTTP 1.1 multipart request (with callbacks and stream) -$req = Mojo::Message::Request->new; -my $stream = ''; -$req->content->on( - body => sub { - my $single = shift; - $single->on( - upgrade => sub { - my ($single, $multi) = @_; - $multi->on( - part => sub { - my ($multi, $part) = @_; - $part->on( - body => sub { - my $part = shift; - return unless $part->headers->content_disposition =~ /hello\.pl/; - $part->on( - read => sub { - my ($part, $chunk) = @_; - $stream .= $chunk; - } - ); - } - ); - } - ); - } - ); - } -); -$req->parse("GET /foo/bar/baz.html?foo13 HTTP/1.1\x0d\x0a"); -$req->parse("Content-Length: 418\x0d\x0a"); -$req->parse('Content-Type: multipart/form-data; bo'); -$req->parse("undary=----------0xKhTmLbOuNdArY\x0d\x0a\x0d\x0a"); -$req->parse("\x0d\x0a------------0xKhTmLbOuNdArY\x0d\x0a"); -$req->parse("Content-Disposition: form-data; name=\"text1\"\x0d\x0a"); -$req->parse("\x0d\x0ahallo welt test123\n"); -$req->parse("\x0d\x0a------------0xKhTmLbOuNdArY\x0d\x0a"); -$req->parse("Content-Disposition: form-data; name=\"text2\"\x0d\x0a"); -$req->parse("\x0d\x0a\x0d\x0a------------0xKhTmLbOuNdArY\x0d\x0a"); -$req->parse('Content-Disposition: form-data; name="upload"; file'); -$req->parse("name=\"hello.pl\"\x0d\x0a"); -$req->parse("Content-Type: application/octet-stream\x0d\x0a\x0d\x0a"); -is $stream, '', 'no content'; -$req->parse("#!/usr/bin/perl\n\n"); -is $stream, '', 'no content'; -$req->parse("use strict;\n"); -is $stream, '', 'no content'; -$req->parse("use warnings;\n\n"); -is $stream, '#!/usr/bin/', 'right content'; -$req->parse("print \"Hello World :)\\n\"\n"); -is $stream, "#!/usr/bin/perl\n\nuse strict;\nuse war", 'right content'; -$req->parse("\x0d\x0a------------0xKhTmLbOuNdArY--"); -ok $req->is_finished, 'request is finished'; -ok $req->content->is_multipart, 'multipart content'; -is $req->method, 'GET', 'right method'; -is $req->version, '1.1', 'right version'; -is $req->url, '/foo/bar/baz.html?foo13', 'right URL'; -is $req->query_params, 'foo13', 'right parameters'; -is $req->headers->content_type, 'multipart/form-data; boundary=----------0xKhTmLbOuNdArY', 'right "Content-Type" value'; -is $req->headers->content_length, 418, 'right "Content-Length" value'; -ok !$req->content->parts->[0]->is_multipart, 'no multipart content'; -ok !$req->content->parts->[1]->is_multipart, 'no multipart content'; -ok !$req->content->parts->[2]->is_multipart, 'no multipart content'; -is $req->content->parts->[0]->asset->slurp, "hallo welt test123\n", 'right content'; -is $req->body_params->to_hash->{text1}, "hallo welt test123\n", 'right value'; -is $req->body_params->to_hash->{text2}, '', 'right value'; -is $stream, "#!/usr/bin/perl\n\n" . "use strict;\n" . "use warnings;\n\n" . "print \"Hello World :)\\n\"\n", - 'right content'; - -# Parse HTTP 1.1 multipart request (without upgrade) -$req = Mojo::Message::Request->new; -$req->content->auto_upgrade(0); -$req->parse("GET /foo/bar/baz.html?foo13 HTTP/1.1\x0d\x0a"); -$req->parse("Content-Length: 418\x0d\x0a"); -$req->parse('Content-Type: multipart/form-data; bo'); -$req->parse("undary=----------0xKhTmLbOuNdArY\x0d\x0a\x0d\x0a"); -$req->parse("\x0d\x0a------------0xKhTmLbOuNdArY\x0d\x0a"); -$req->parse("Content-Disposition: form-data; name=\"text1\"\x0d\x0a"); -$req->parse("\x0d\x0ahallo welt test123\n"); -$req->parse("\x0d\x0a------------0xKhTmLbOuNdArY\x0d\x0a"); -$req->parse("Content-Disposition: form-data; name=\"text2\"\x0d\x0a"); -$req->parse("\x0d\x0a\x0d\x0a------------0xKhTmLbOuNdArY\x0d\x0a"); -$req->parse('Content-Disposition: form-data; name="upload"; file'); -$req->parse("name=\"hello.pl\"\x0d\x0a"); -$req->parse("Content-Type: application/octet-stream\x0d\x0a\x0d\x0a"); -$req->parse("#!/usr/bin/perl\n\n"); -$req->parse("use strict;\n"); -$req->parse("use warnings;\n\n"); -$req->parse("print \"Hello World :)\\n\"\n"); -$req->parse("\x0d\x0a------------0xKhTmLbOuNdArY--"); -ok $req->is_finished, 'request is finished'; -ok !$req->content->is_multipart, 'no multipart content'; -is $req->method, 'GET', 'right method'; -is $req->version, '1.1', 'right version'; -is $req->url, '/foo/bar/baz.html?foo13', 'right URL'; -is $req->query_params, 'foo13', 'right parameters'; -is $req->headers->content_type, 'multipart/form-data; boundary=----------0xKhTmLbOuNdArY', 'right "Content-Type" value'; -is $req->headers->content_length, 418, 'right "Content-Length" value'; -ok !$req->content->is_multipart, 'no multipart content'; -like $req->content->asset->slurp, qr/------------0xKhTmLbOuNdArY--$/, 'right content'; - -# Parse HTTP 1.1 multipart request with "0" filename -$req = Mojo::Message::Request->new; -$req->parse("GET /foo/bar/baz.html?foo13 HTTP/1.1\x0d\x0a"); -$req->parse("Content-Length: 410\x0d\x0a"); -$req->parse('Content-Type: multipart/form-data; bo'); -$req->parse("undary=----------0xKhTmLbOuNdArY\x0d\x0a\x0d\x0a"); -$req->parse("\x0d\x0a------------0xKhTmLbOuNdArY\x0d\x0a"); -$req->parse("Content-Disposition: form-data; name=\"text1\"\x0d\x0a"); -$req->parse("\x0d\x0ahallo welt test123\n"); -$req->parse("\x0d\x0a------------0xKhTmLbOuNdArY\x0d\x0a"); -$req->parse("Content-Disposition: form-data; name=\"text2\"\x0d\x0a"); -$req->parse("\x0d\x0a\x0d\x0a------------0xKhTmLbOuNdArY\x0d\x0a"); -$req->parse('Content-Disposition: form-data; name="upload"; file'); -$req->parse("name=\"0\"\x0d\x0a"); -$req->parse("Content-Type: application/octet-stream\x0d\x0a\x0d\x0a"); -$req->parse("#!/usr/bin/perl\n\n"); -$req->parse("use strict;\n"); -$req->parse("use warnings;\n\n"); -$req->parse("print \"Hello World :)\\n\"\n"); -$req->parse("\x0d\x0a------------0xKhTmLbOuNdArY--"); -ok $req->is_finished, 'request is finished'; -ok $req->content->is_multipart, 'no multipart content'; -is $req->method, 'GET', 'right method'; -is $req->version, '1.1', 'right version'; -is $req->url, '/foo/bar/baz.html?foo13', 'right URL'; -is $req->query_params, 'foo13', 'right parameters'; -is $req->headers->content_type, 'multipart/form-data; boundary=----------0xKhTmLbOuNdArY', 'right "Content-Type" value'; -is $req->headers->content_length, 410, 'right "Content-Length" value'; -ok !$req->content->parts->[0]->is_multipart, 'no multipart content'; -ok !$req->content->parts->[1]->is_multipart, 'no multipart content'; -ok !$req->content->parts->[2]->is_multipart, 'no multipart content'; -is $req->content->parts->[0]->asset->slurp, "hallo welt test123\n", 'right content'; -is $req->body_params->to_hash->{text1}, "hallo welt test123\n", 'right value'; -is $req->body_params->to_hash->{text2}, '', 'right value'; -is $req->body_params->to_hash->{upload}, undef, 'not a body parameter'; -is $req->upload('upload')->filename, '0', 'right filename'; -ok !$req->upload('upload')->asset->is_file, 'stored in memory'; -is $req->upload('upload')->asset->size, 69, 'right size'; - -# Parse full HTTP 1.1 proxy request with basic authentication -$req = Mojo::Message::Request->new; -$req->parse("GET http://127.0.0.1/foo/bar#baz HTTP/1.1\x0d\x0a"); -$req->parse("Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==\x0d\x0a"); -$req->parse("Host: 127.0.0.1\x0d\x0a"); -$req->parse("Proxy-Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==\x0d\x0a"); -$req->parse("Content-Length: 13\x0d\x0a\x0d\x0a"); -$req->parse("Hello World!\n"); -ok $req->is_finished, 'request is finished'; -is $req->method, 'GET', 'right method'; -is $req->version, '1.1', 'right version'; -is $req->url->base, 'http://127.0.0.1', 'right base URL'; -is $req->url->base->userinfo, 'Aladdin:open sesame', 'right base userinfo'; -is $req->url, 'http://127.0.0.1/foo/bar', 'right URL'; -is $req->proxy->userinfo, 'Aladdin:open sesame', 'right proxy userinfo'; - -# Parse full HTTP 1.1 proxy connect request with basic authentication -$req = Mojo::Message::Request->new; -$req->parse("CONNECT 127.0.0.1:3000 HTTP/1.1\x0d\x0a"); -$req->parse("Host: 127.0.0.1:3000\x0d\x0a"); -$req->parse("Proxy-Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==\x0d\x0a"); -$req->parse("Content-Length: 0\x0d\x0a\x0d\x0a"); -ok $req->is_finished, 'request is finished'; -is $req->method, 'CONNECT', 'right method'; -is $req->version, '1.1', 'right version'; -is $req->url, '//127.0.0.1:3000', 'right URL'; -is $req->url->host, '127.0.0.1', 'right host'; -is $req->url->port, '3000', 'right port'; -is $req->proxy->userinfo, 'Aladdin:open sesame', 'right proxy userinfo'; - -# Build minimal HTTP 1.1 request -$req = Mojo::Message::Request->new; -$req->method('GET'); -$req->url->parse('http://127.0.0.1/'); -$req = Mojo::Message::Request->new->parse($req->to_string); -ok $req->is_finished, 'request is finished'; -is $req->method, 'GET', 'right method'; -is $req->version, '1.1', 'right version'; -is $req->url, '/', 'right URL'; -is $req->url->to_abs, 'http://127.0.0.1/', 'right absolute URL'; -is $req->headers->host, '127.0.0.1', 'right "Host" value'; - -# Build HTTP 1.1 start-line and header -$req = Mojo::Message::Request->new; -$req->method('GET'); -$req->url->parse('http://127.0.0.1/foo/bar'); -$req->headers->expect('100-continue'); -$req = Mojo::Message::Request->new->parse($req->to_string); -ok $req->is_finished, 'request is finished'; -is $req->method, 'GET', 'right method'; -is $req->version, '1.1', 'right version'; -is $req->url, '/foo/bar', 'right URL'; -is $req->url->to_abs, 'http://127.0.0.1/foo/bar', 'right absolute URL'; -is $req->headers->expect, '100-continue', 'right "Expect" value'; -is $req->headers->host, '127.0.0.1', 'right "Host" value'; - -# Build HTTP 1.1 start-line and header (with clone) -$req = Mojo::Message::Request->new; -$req->method('GET'); -$req->url->parse('http://127.0.0.1/foo/bar'); -$req->headers->expect('100-continue'); -my $clone = $req->clone; -$req = Mojo::Message::Request->new->parse($req->to_string); -ok $req->is_finished, 'request is finished'; -is $req->method, 'GET', 'right method'; -is $req->version, '1.1', 'right version'; -is $req->url, '/foo/bar', 'right URL'; -is $req->url->to_abs, 'http://127.0.0.1/foo/bar', 'right absolute URL'; -is $req->headers->expect, '100-continue', 'right "Expect" value'; -is $req->headers->host, '127.0.0.1', 'right "Host" value'; -$clone = Mojo::Message::Request->new->parse($clone->to_string); -ok $clone->is_finished, 'request is finished'; -is $clone->method, 'GET', 'right method'; -is $clone->version, '1.1', 'right version'; -is $clone->url, '/foo/bar', 'right URL'; -is $clone->url->to_abs, 'http://127.0.0.1/foo/bar', 'right absolute URL'; -is $clone->headers->expect, '100-continue', 'right "Expect" value'; -is $clone->headers->host, '127.0.0.1', 'right "Host" value'; - -# Build HTTP 1.1 start-line and header (with clone and changes) -$req = Mojo::Message::Request->new; -$req->method('GET'); -$req->url->parse('http://127.0.0.1/foo/bar'); -$req->headers->expect('100-continue'); -$clone = $req->clone; -$clone->method('POST'); -$clone->headers->expect('nothing'); -$clone->version('1.2'); -push @{$clone->url->path->parts}, 'baz'; -$req = Mojo::Message::Request->new->parse($req->to_string); -ok $req->is_finished, 'request is finished'; -is $req->method, 'GET', 'right method'; -is $req->version, '1.1', 'right version'; -is $req->url, '/foo/bar', 'right URL'; -is $req->url->to_abs, 'http://127.0.0.1/foo/bar', 'right absolute URL'; -is $req->headers->expect, '100-continue', 'right "Expect" value'; -is $req->headers->host, '127.0.0.1', 'right "Host" value'; -$clone = Mojo::Message::Request->new->parse($clone->to_string); -ok $clone->is_finished, 'request is finished'; -is $clone->method, 'POST', 'right method'; -is $clone->version, '1.2', 'right version'; -is $clone->url, '/foo/bar/baz', 'right URL'; -is $clone->url->to_abs, 'http://127.0.0.1/foo/bar/baz', 'right absolute URL'; -is $clone->headers->expect, 'nothing', 'right "Expect" value'; -is $clone->headers->host, '127.0.0.1', 'right "Host" value'; - -# Build full HTTP 1.1 request -$req = Mojo::Message::Request->new; -$finished = undef; -$req->on(finish => sub { $finished = shift->is_finished }); -$req->method('get'); -$req->url->parse('http://127.0.0.1/foo/bar'); -$req->headers->expect('100-continue'); -$req->body("Hello World!\n"); -$req = Mojo::Message::Request->new->parse($req->to_string); -ok $req->is_finished, 'request is finished'; -is $req->method, 'GET', 'right method'; -is $req->version, '1.1', 'right version'; -is $req->url, '/foo/bar', 'right URL'; -is $req->url->to_abs, 'http://127.0.0.1/foo/bar', 'right absolute URL'; -is $req->headers->expect, '100-continue', 'right "Expect" value'; -is $req->headers->host, '127.0.0.1', 'right "Host" value'; -is $req->headers->content_length, '13', 'right "Content-Length" value'; -is $req->body, "Hello World!\n", 'right content'; -ok $finished, 'finish event has been emitted'; -ok $req->is_finished, 'request is finished'; - -# Build HTTP 1.1 request parts with progress -$req = Mojo::Message::Request->new; -my $state; -$finished = undef; -$progress = 0; -$req->on(finish => sub { $finished = shift->is_finished }); -$req->on( - progress => sub { - my ($req, $part, $offset) = @_; - $state = $part; - $progress += $offset; - } -); -$req->method('get'); -$req->url->parse('http://127.0.0.1/foo/bar'); -$req->headers->expect('100-continue'); -$req->body("Hello World!\n"); -ok !$state, 'no state'; -ok !$progress, 'no progress'; -ok !$finished, 'not finished'; -ok $req->build_start_line, 'built start-line'; -is $state, 'start_line', 'made progress on start_line'; -ok $progress, 'made progress'; -$progress = 0; -ok !$finished, 'not finished'; -ok $req->build_headers, 'built headers'; -is $state, 'headers', 'made progress on headers'; -ok $progress, 'made progress'; -$progress = 0; -ok !$finished, 'not finished'; -ok $req->build_body, 'built body'; -is $state, 'body', 'made progress on headers'; -ok $progress, 'made progress'; -ok $finished, 'finished'; - -# Build full HTTP 1.1 request (with clone) -$req = Mojo::Message::Request->new; -$finished = undef; -$req->on(finish => sub { $finished = shift->is_finished }); -$req->method('get'); -$req->url->parse('http://127.0.0.1/foo/bar'); -$req->headers->expect('100-continue'); -$req->body("Hello World!\n"); -$clone = $req->clone; -$req = Mojo::Message::Request->new->parse($req->to_string); -ok $req->is_finished, 'request is finished'; -is $req->method, 'GET', 'right method'; -is $req->version, '1.1', 'right version'; -is $req->url, '/foo/bar', 'right URL'; -is $req->url->to_abs, 'http://127.0.0.1/foo/bar', 'right absolute URL'; -is $req->headers->expect, '100-continue', 'right "Expect" value'; -is $req->headers->host, '127.0.0.1', 'right "Host" value'; -is $req->headers->content_length, '13', 'right "Content-Length" value'; -is $req->body, "Hello World!\n", 'right content'; -ok $finished, 'finish event has been emitted'; -ok $req->is_finished, 'request is finished'; -$finished = undef; -$clone = Mojo::Message::Request->new->parse($clone->to_string); -ok $clone->is_finished, 'request is finished'; -is $clone->method, 'GET', 'right method'; -is $clone->version, '1.1', 'right version'; -is $clone->url, '/foo/bar', 'right URL'; -is $clone->url->to_abs, 'http://127.0.0.1/foo/bar', 'right absolute URL'; -is $clone->headers->expect, '100-continue', 'right "Expect" value'; -is $clone->headers->host, '127.0.0.1', 'right "Host" value'; -is $clone->headers->content_length, '13', 'right "Content-Length" value'; -is $clone->body, "Hello World!\n", 'right content'; -ok !$finished, 'finish event has been emitted'; -ok $clone->is_finished, 'request is finished'; - -# Build full HTTP 1.1 request (roundtrip) -$req = Mojo::Message::Request->new; -$req->method('GET'); -$req->url->parse('http://127.0.0.1/foo/bar'); -$req->headers->expect('100-continue'); -$req->body("Hello World!\n"); -$req = Mojo::Message::Request->new->parse($req->to_string); -ok $req->is_finished, 'request is finished'; -is $req->method, 'GET', 'right method'; -is $req->version, '1.1', 'right version'; -is $req->url, '/foo/bar', 'right URL'; -is $req->url->to_abs, 'http://127.0.0.1/foo/bar', 'right absolute URL'; -is $req->headers->expect, '100-continue', 'right "Expect" value'; -is $req->headers->host, '127.0.0.1', 'right "Host" value'; -is $req->headers->content_length, '13', 'right "Content-Length" value'; -is $req->body, "Hello World!\n", 'right content'; -my $req2 = Mojo::Message::Request->new->parse($req->to_string); -is $req->content->leftovers, '', 'no leftovers'; -is $req->error, undef, 'no error'; -is $req2->content->leftovers, '', 'no leftovers'; -is $req2->error, undef, 'no error'; -ok $req2->is_finished, 'request is finished'; -is $req2->method, 'GET', 'right method'; -is $req2->version, '1.1', 'right version'; -is $req2->url, '/foo/bar', 'right URL'; -is $req2->url->to_abs, 'http://127.0.0.1/foo/bar', 'right absolute URL'; -is $req2->headers->expect, '100-continue', 'right "Expect" value'; -is $req2->headers->host, '127.0.0.1', 'right "Host" value'; -is $req->headers->content_length, '13', 'right "Content-Length" value'; -is $req->body, "Hello World!\n", 'right content'; - -# Build HTTP 1.1 request body -$req = Mojo::Message::Request->new; -$finished = undef; -$req->on(finish => sub { $finished = shift->is_finished }); -$req->method('get'); -$req->url->parse('http://127.0.0.1/foo/bar'); -$req->headers->expect('100-continue'); -$req->body("Hello World!\n"); -my $i = 0; -while (my $chunk = $req->get_body_chunk($i)) { $i += length $chunk } -ok $finished, 'finish event has been emitted'; -ok $req->is_finished, 'request is finished'; - -# Build WebSocket handshake request -$req = Mojo::Message::Request->new; -$req->method('GET'); -$req->url->parse('http://example.com/demo'); -$req->headers->host('example.com'); -$req->headers->connection('Upgrade'); -$req->headers->sec_websocket_accept('abcdef='); -$req->headers->sec_websocket_protocol('sample'); -$req->headers->upgrade('websocket'); -$req = Mojo::Message::Request->new->parse($req->to_string); -ok $req->is_finished, 'request is finished'; -is $req->method, 'GET', 'right method'; -is $req->version, '1.1', 'right version'; -is $req->url, '/demo', 'right URL'; -is $req->url->to_abs, 'http://example.com/demo', 'right absolute URL'; -is $req->headers->connection, 'Upgrade', 'right "Connection" value'; -is $req->headers->upgrade, 'websocket', 'right "Upgrade" value'; -is $req->headers->host, 'example.com', 'right "Host" value'; -is $req->headers->content_length, 0, 'right "Content-Length" value'; -is $req->headers->sec_websocket_accept, 'abcdef=', 'right "Sec-WebSocket-Key" value'; -is $req->headers->sec_websocket_protocol, 'sample', 'right "Sec-WebSocket-Protocol" value'; -is $req->body, '', 'no content'; -ok $finished, 'finish event has been emitted'; -ok $req->is_finished, 'request is finished'; - -# Build WebSocket handshake request (with clone) -$req = Mojo::Message::Request->new; -$req->method('GET'); -$req->url->parse('http://example.com/demo'); -$req->headers->host('example.com'); -$req->headers->connection('Upgrade'); -$req->headers->sec_websocket_accept('abcdef='); -$req->headers->sec_websocket_protocol('sample'); -$req->headers->upgrade('websocket'); -$clone = $req->clone; -$req = Mojo::Message::Request->new->parse($req->to_string); -ok $req->is_finished, 'request is finished'; -is $req->method, 'GET', 'right method'; -is $req->version, '1.1', 'right version'; -is $req->url, '/demo', 'right URL'; -is $req->url->to_abs, 'http://example.com/demo', 'right absolute URL'; -is $req->headers->connection, 'Upgrade', 'right "Connection" value'; -is $req->headers->upgrade, 'websocket', 'right "Upgrade" value'; -is $req->headers->host, 'example.com', 'right "Host" value'; -is $req->headers->content_length, 0, 'right "Content-Length" value'; -is $req->headers->sec_websocket_accept, 'abcdef=', 'right "Sec-WebSocket-Key" value'; -is $req->headers->sec_websocket_protocol, 'sample', 'right "Sec-WebSocket-Protocol" value'; -is $req->body, '', 'no content'; -ok $req->is_finished, 'request is finished'; -$clone = Mojo::Message::Request->new->parse($clone->to_string); -ok $clone->is_finished, 'request is finished'; -is $clone->method, 'GET', 'right method'; -is $clone->version, '1.1', 'right version'; -is $clone->url, '/demo', 'right URL'; -is $clone->url->to_abs, 'http://example.com/demo', 'right absolute URL'; -is $clone->headers->connection, 'Upgrade', 'right "Connection" value'; -is $clone->headers->upgrade, 'websocket', 'right "Upgrade" value'; -is $clone->headers->host, 'example.com', 'right "Host" value'; -is $req->headers->content_length, 0, 'right "Content-Length" value'; -is $clone->headers->sec_websocket_accept, 'abcdef=', 'right "Sec-WebSocket-Key" value'; -is $clone->headers->sec_websocket_protocol, 'sample', 'right "Sec-WebSocket-Protocol" value'; -is $clone->body, '', 'no content'; -ok $clone->is_finished, 'request is finished'; - -# Build WebSocket handshake proxy request -$req = Mojo::Message::Request->new; -$req->method('GET'); -$req->url->parse('http://example.com/demo'); -$req->headers->host('example.com'); -$req->headers->connection('Upgrade'); -$req->headers->sec_websocket_accept('abcdef='); -$req->headers->sec_websocket_protocol('sample'); -$req->headers->upgrade('websocket'); -$req->proxy(Mojo::URL->new('http://127.0.0.2:8080')); -$req = Mojo::Message::Request->new->parse($req->to_string); -ok $req->is_finished, 'request is finished'; -is $req->method, 'GET', 'right method'; -is $req->version, '1.1', 'right version'; -is $req->url, '/demo', 'right URL'; -is $req->url->to_abs, 'http://example.com/demo', 'right absolute URL'; -is $req->headers->connection, 'Upgrade', 'right "Connection" value'; -is $req->headers->upgrade, 'websocket', 'right "Upgrade" value'; -is $req->headers->host, 'example.com', 'right "Host" value'; -is $req->headers->content_length, 0, 'right "Content-Length" value'; -is $req->headers->sec_websocket_accept, 'abcdef=', 'right "Sec-WebSocket-Key" value'; -is $req->headers->sec_websocket_protocol, 'sample', 'right "Sec-WebSocket-Protocol" value'; -is $req->body, '', 'no content'; -ok $finished, 'finish event has been emitted'; -ok $req->is_finished, 'request is finished'; - -# Build full HTTP 1.1 proxy request -$req = Mojo::Message::Request->new; -$req->method('GET'); -$req->url->parse('http://127.0.0.1/foo/bar'); -$req->headers->expect('100-continue'); -$req->body("Hello World!\n"); -$req->proxy(Mojo::URL->new('http://127.0.0.2:8080')); -$req = Mojo::Message::Request->new->parse($req->to_string); -ok $req->is_finished, 'request is finished'; -is $req->method, 'GET', 'right method'; -is $req->version, '1.1', 'right version'; -is $req->url, 'http://127.0.0.1/foo/bar', 'right URL'; -is $req->url->to_abs, 'http://127.0.0.1/foo/bar', 'right absolute URL'; -is $req->headers->expect, '100-continue', 'right "Expect" value'; -is $req->headers->host, '127.0.0.1', 'right "Host" value'; -is $req->headers->content_length, '13', 'right "Content-Length" value'; -is $req->body, "Hello World!\n", 'right content'; - -# Build full HTTP 1.1 proxy request (proxy disabled) -$req = Mojo::Message::Request->new; -$req->method('GET'); -$req->url->parse('http://127.0.0.1/foo/bar'); -$req->body("Hello World!\n"); -$req->via_proxy(0)->proxy(Mojo::URL->new('http://127.0.0.2:8080')); -$req = Mojo::Message::Request->new->parse($req->to_string); -ok $req->is_finished, 'request is finished'; -is $req->method, 'GET', 'right method'; -is $req->version, '1.1', 'right version'; -is $req->url, '/foo/bar', 'right URL'; -is $req->url->to_abs, 'http://127.0.0.1/foo/bar', 'right absolute URL'; -is $req->headers->host, '127.0.0.1', 'right "Host" value'; -is $req->headers->content_length, '13', 'right "Content-Length" value'; -is $req->body, "Hello World!\n", 'right content'; - -# Build full HTTP 1.1 proxy request (HTTPS) -$req = Mojo::Message::Request->new; -$req->method('GET'); -$req->url->parse('https://127.0.0.1/foo/bar'); -$req->headers->expect('100-continue'); -$req->body("Hello World!\n"); -$req->proxy(Mojo::URL->new('http://127.0.0.2:8080')); -$req = Mojo::Message::Request->new->parse($req->to_string); -ok $req->is_finished, 'request is finished'; -is $req->method, 'GET', 'right method'; -is $req->version, '1.1', 'right version'; -is $req->url, '/foo/bar', 'right URL'; -is $req->url->to_abs, 'http://127.0.0.1/foo/bar', 'right absolute URL'; -is $req->headers->expect, '100-continue', 'right "Expect" value'; -is $req->headers->host, '127.0.0.1', 'right "Host" value'; -is $req->headers->content_length, '13', 'right "Content-Length" value'; -is $req->body, "Hello World!\n", 'right content'; - -# Build full HTTP 1.1 proxy request with basic authentication -$req = Mojo::Message::Request->new; -$req->method('GET'); -$req->url->parse('http://Aladdin:open%20sesame@127.0.0.1/foo/bar'); -$req->headers->expect('100-continue'); -$req->body("Hello World!\n"); -$req->proxy(Mojo::URL->new('http://Aladdin:open%20sesame@127.0.0.2:8080')); -$req = Mojo::Message::Request->new->parse($req->to_string); -ok $req->is_finished, 'request is finished'; -is $req->method, 'GET', 'right method'; -is $req->version, '1.1', 'right version'; -is $req->url, 'http://127.0.0.1/foo/bar', 'right URL'; -is $req->url->to_abs, 'http://127.0.0.1/foo/bar', 'right absolute URL'; -is $req->proxy->userinfo, 'Aladdin:open sesame', 'right proxy userinfo'; -is $req->headers->authorization, 'Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==', 'right "Authorization" value'; -is $req->headers->expect, '100-continue', 'right "Expect" value'; -is $req->headers->host, '127.0.0.1', 'right "Host" value'; -is $req->headers->proxy_authorization, 'Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==', 'right "Proxy-Authorization" value'; -is $req->headers->content_length, '13', 'right "Content-Length" value'; -is $req->body, "Hello World!\n", 'right content'; - -# Build full HTTP 1.1 proxy request with basic authentication (and clone) -$req = Mojo::Message::Request->new; -$req->method('GET'); -$req->url->parse('http://Aladdin:open%20sesame@127.0.0.1/foo/bar'); -$req->headers->expect('100-continue'); -$req->body("Hello World!\n"); -$req->proxy(Mojo::URL->new('http://Aladdin:open%20sesame@127.0.0.2:8080')); -$clone = $req->clone; -$req = Mojo::Message::Request->new->parse($req->to_string); -ok $req->is_finished, 'request is finished'; -is $req->method, 'GET', 'right method'; -is $req->version, '1.1', 'right version'; -is $req->url, 'http://127.0.0.1/foo/bar', 'right URL'; -is $req->url->to_abs, 'http://127.0.0.1/foo/bar', 'right absolute URL'; -is $req->proxy->userinfo, 'Aladdin:open sesame', 'right proxy userinfo'; -is $req->headers->authorization, 'Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==', 'right "Authorization" value'; -is $req->headers->expect, '100-continue', 'right "Expect" value'; -is $req->headers->host, '127.0.0.1', 'right "Host" value'; -is $req->headers->proxy_authorization, 'Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==', 'right "Proxy-Authorization" value'; -is $req->headers->content_length, '13', 'right "Content-Length" value'; -is $req->body, "Hello World!\n", 'right content'; -$clone = Mojo::Message::Request->new->parse($clone->to_string); -ok $clone->is_finished, 'request is finished'; -is $clone->method, 'GET', 'right method'; -is $clone->version, '1.1', 'right version'; -is $clone->url, 'http://127.0.0.1/foo/bar', 'right URL'; -is $clone->url->to_abs, 'http://127.0.0.1/foo/bar', 'right absolute URL'; -is $clone->proxy->userinfo, 'Aladdin:open sesame', 'right proxy userinfo'; -is $clone->headers->authorization, 'Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==', 'right "Authorization" value'; -is $clone->headers->expect, '100-continue', 'right "Expect" value'; -is $clone->headers->host, '127.0.0.1', 'right "Host" value'; -is $clone->headers->proxy_authorization, 'Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==', 'right "Proxy-Authorization" value'; -is $clone->headers->content_length, '13', 'right "Content-Length" value'; -is $clone->body, "Hello World!\n", 'right content'; - -# Build full HTTP 1.1 proxy connect request with basic authentication -$req = Mojo::Message::Request->new; -$req->method('CONNECT'); -$req->url->parse('http://Aladdin:open%20sesame@bücher.ch:3000/foo/bar'); -$req->proxy(Mojo::URL->new('http://Aladdin:open%20sesame@127.0.0.2:8080')); -$req = Mojo::Message::Request->new->parse($req->to_string); -ok $req->is_finished, 'request is finished'; -is $req->method, 'CONNECT', 'right method'; -is $req->version, '1.1', 'right version'; -is $req->url, '//xn--bcher-kva.ch:3000', 'right URL'; -is $req->url->host, 'xn--bcher-kva.ch', 'right host'; -is $req->url->port, '3000', 'right port'; -is $req->url->to_abs, 'http://xn--bcher-kva.ch:3000', 'right absolute URL'; -is $req->proxy->userinfo, 'Aladdin:open sesame', 'right proxy userinfo'; -is $req->headers->authorization, 'Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==', 'right "Authorization" value'; -is $req->headers->host, 'xn--bcher-kva.ch:3000', 'right "Host" value'; -is $req->headers->proxy_authorization, 'Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==', 'right "Proxy-Authorization" value'; - -# Build HTTP 1.1 multipart request -$req = Mojo::Message::Request->new; -$req->method('GET'); -$req->url->parse('http://127.0.0.1/foo/bar'); -$req->content(Mojo::Content::MultiPart->new); -$req->headers->content_type('multipart/mixed; boundary=7am1X'); -push @{$req->content->parts}, Mojo::Content::Single->new; -$req->content->parts->[-1]->asset->add_chunk('Hallo Welt lalalala!'); -my $content = Mojo::Content::Single->new; -$content->asset->add_chunk("lala\nfoobar\nperl rocks\n"); -$content->headers->content_type('text/plain'); -push @{$req->content->parts}, $content; -$req = Mojo::Message::Request->new->parse($req->to_string); -ok $req->is_finished, 'request is finished'; -is $req->method, 'GET', 'right method'; -is $req->version, '1.1', 'right version'; -is $req->url, '/foo/bar', 'right URL'; -is $req->url->to_abs, 'http://127.0.0.1/foo/bar', 'right absolute URL'; -is $req->headers->host, '127.0.0.1', 'right "Host" value'; -is $req->headers->content_length, '106', 'right "Content-Length" value'; -is $req->headers->content_type, 'multipart/mixed; boundary=7am1X', 'right "Content-Type" value'; -is $req->content->parts->[0]->asset->slurp, 'Hallo Welt lalalala!', 'right content'; -is $req->content->parts->[1]->headers->content_type, 'text/plain', 'right "Content-Type" value'; -is $req->content->parts->[1]->asset->slurp, "lala\nfoobar\nperl rocks\n", 'right content'; - -# Build HTTP 1.1 multipart request (with clone) -$req = Mojo::Message::Request->new; -$req->method('GET'); -$req->url->parse('http://127.0.0.1/foo/bar'); -$req->content(Mojo::Content::MultiPart->new); -$req->headers->content_type('multipart/mixed; boundary=7am1X'); -push @{$req->content->parts}, Mojo::Content::Single->new; -$req->content->parts->[-1]->asset->add_chunk('Hallo Welt lalalala!'); -$content = Mojo::Content::Single->new; -$content->asset->add_chunk("lala\nfoobar\nperl rocks\n"); -$content->headers->content_type('text/plain'); -push @{$req->content->parts}, $content; -$clone = $req->clone; -$req = Mojo::Message::Request->new->parse($req->to_string); -ok $req->is_finished, 'request is finished'; -is $req->method, 'GET', 'right method'; -is $req->version, '1.1', 'right version'; -is $req->url, '/foo/bar', 'right URL'; -is $req->url->to_abs, 'http://127.0.0.1/foo/bar', 'right absolute URL'; -is $req->headers->host, '127.0.0.1', 'right "Host" value'; -is $req->headers->content_length, '106', 'right "Content-Length" value'; -is $req->headers->content_type, 'multipart/mixed; boundary=7am1X', 'right "Content-Type" value'; -is $req->content->parts->[0]->asset->slurp, 'Hallo Welt lalalala!', 'right content'; -is $req->content->parts->[1]->headers->content_type, 'text/plain', 'right "Content-Type" value'; -is $req->content->parts->[1]->asset->slurp, "lala\nfoobar\nperl rocks\n", 'right content'; -$clone = Mojo::Message::Request->new->parse($clone->to_string); -ok $clone->is_finished, 'request is finished'; -is $clone->method, 'GET', 'right method'; -is $clone->version, '1.1', 'right version'; -is $clone->url, '/foo/bar', 'right URL'; -is $clone->url->to_abs, 'http://127.0.0.1/foo/bar', 'right absolute URL'; -is $clone->headers->host, '127.0.0.1', 'right "Host" value'; -is $clone->headers->content_length, '106', 'right "Content-Length" value'; -is $clone->headers->content_type, 'multipart/mixed; boundary=7am1X', 'right "Content-Type" value'; -is $clone->content->parts->[0]->asset->slurp, 'Hallo Welt lalalala!', 'right content'; -is $clone->content->parts->[1]->headers->content_type, 'text/plain', 'right "Content-Type" value'; -is $clone->content->parts->[1]->asset->slurp, "lala\nfoobar\nperl rocks\n", 'right content'; - -# Build HTTP 1.1 chunked request -$req = Mojo::Message::Request->new; -$req->method('GET'); -$req->url->parse('http://127.0.0.1:8080/foo/bar'); -$req->headers->transfer_encoding('chunked'); -my $counter; -$req->on(progress => sub { $counter++ }); -$req->content->write_chunk( - 'hello world!' => sub { - shift->write_chunk( - "hello world2!\n\n" => sub { - my $self = shift; - $self->write_chunk(''); - } - ); - } -); -is $req->clone, undef, 'dynamic requests cannot be cloned'; -$req = Mojo::Message::Request->new->parse($req->to_string); -ok $req->is_finished, 'request is finished'; -is $req->method, 'GET', 'right method'; -is $req->version, '1.1', 'right version'; -is $req->url, '/foo/bar', 'right URL'; -is $req->url->to_abs, 'http://127.0.0.1:8080/foo/bar', 'right absolute URL'; -is $req->headers->host, '127.0.0.1:8080', 'right "Host" value'; -is $req->headers->transfer_encoding, undef, 'no "Transfer-Encoding" value'; -is $req->body, "hello world!hello world2!\n\n", 'right content'; -ok $counter, 'right counter'; - -# Build HTTP 1.1 chunked request -$req = Mojo::Message::Request->new; -$req->method('GET'); -$req->url->parse('http://127.0.0.1'); -$req->content->write_chunk('hello world!'); -$req->content->write_chunk("hello world2!\n\n"); -$req->content->write_chunk(''); -is $req->clone, undef, 'dynamic requests cannot be cloned'; -$req = Mojo::Message::Request->new->parse($req->to_string); -ok $req->is_finished, 'request is finished'; -is $req->method, 'GET', 'right method'; -is $req->version, '1.1', 'right version'; -is $req->url, '/', 'right URL'; -is $req->url->to_abs, 'http://127.0.0.1/', 'right absolute URL'; -is $req->headers->host, '127.0.0.1', 'right "Host" value'; -is $req->headers->transfer_encoding, undef, 'no "Transfer-Encoding" value'; -is $req->body, "hello world!hello world2!\n\n", 'right content'; - -# Build full HTTP 1.1 request with cookies -$req = Mojo::Message::Request->new; -$req->method('GET'); -$req->url->parse('http://127.0.0.1/foo/bar?0'); -$req->headers->expect('100-continue'); -$req->cookies({name => 'foo', value => 'bar'}, {name => 'bar', value => 'baz'}); -$req->cookies(Mojo::Cookie::Request->new(name => 'baz', value => 'yada')); -$req->body("Hello World!\n"); -ok !!$req->to_string, 'message built'; -$req2 = Mojo::Message::Request->new; -$req2->parse($req->to_string); -ok $req2->is_finished, 'request is finished'; -is $req2->method, 'GET', 'right method'; -is $req2->version, '1.1', 'right version'; -is $req2->headers->expect, '100-continue', 'right "Expect" value'; -is $req2->headers->host, '127.0.0.1', 'right "Host" value'; -is $req2->headers->content_length, 13, 'right "Content-Length" value'; -is $req2->headers->cookie, 'foo=bar; bar=baz; baz=yada', 'right "Cookie" value'; -is $req2->url, '/foo/bar?0', 'right URL'; -is $req2->url->to_abs, 'http://127.0.0.1/foo/bar?0', 'right absolute URL'; -ok defined $req2->cookie('foo'), 'cookie "foo" exists'; -ok defined $req2->cookie('bar'), 'cookie "bar" exists'; -ok defined $req2->cookie('baz'), 'cookie "baz" exists'; -ok !defined $req2->cookie('yada'), 'cookie "yada" does not exist'; -is $req2->cookie('foo')->value, 'bar', 'right value'; -is $req2->cookie('bar')->value, 'baz', 'right value'; -is $req2->cookie('baz')->value, 'yada', 'right value'; -is $req2->body, "Hello World!\n", 'right content'; - -# Build HTTP 1.1 request with cookies sharing the same name -$req = Mojo::Message::Request->new; -$req->method('GET'); -$req->url->parse('http://127.0.0.1/foo/bar'); -$req->cookies( - {name => 'foo', value => 'bar'}, - {name => 'foo', value => 'baz'}, - {name => 'foo', value => 'yada'}, - {name => 'bar', value => 'foo'} -); -$req2 = Mojo::Message::Request->new; -$req2->parse($req->to_string); -ok $req2->is_finished, 'request is finished'; -is $req2->method, 'GET', 'right method'; -is $req2->version, '1.1', 'right version'; -is $req2->headers->host, '127.0.0.1', 'right "Host" value'; -is $req2->headers->cookie, 'foo=bar; foo=baz; foo=yada; bar=foo', 'right "Cookie" value'; -is $req2->url, '/foo/bar', 'right URL'; -is $req2->url->to_abs, 'http://127.0.0.1/foo/bar', 'right absolute URL'; -is_deeply [map { $_->value } @{$req2->every_cookie('foo')}], [qw(bar baz yada)], 'right values'; -is_deeply [map { $_->value } @{$req2->every_cookie('bar')}], ['foo'], 'right values'; - -# Parse full HTTP 1.0 request with cookies and progress callback -$req = Mojo::Message::Request->new; -$counter = 0; -$req->on(progress => sub { $counter++ }); -is $counter, 0, 'right count'; -ok !$req->content->is_parsing_body, 'is not parsing body'; -ok !$req->is_finished, 'request is not finished'; -$req->parse('GET /foo/bar/baz.html?fo'); -is $counter, 1, 'right count'; -ok !$req->content->is_parsing_body, 'is not parsing body'; -ok !$req->is_finished, 'request is not finished'; -$req->parse("o=13 HTTP/1.0\x0d\x0aContent"); -is $counter, 2, 'right count'; -ok !$req->content->is_parsing_body, 'is not parsing body'; -ok !$req->is_finished, 'request is not finished'; -$req->parse('-Type: text/'); -is $counter, 3, 'right count'; -ok !$req->content->is_parsing_body, 'is not parsing body'; -ok !$req->is_finished, 'request is not finished'; -$req->parse("plain\x0d\x0a"); -is $counter, 4, 'right count'; -ok !$req->content->is_parsing_body, 'is not parsing body'; -ok !$req->is_finished, 'request is not finished'; -$req->parse('Cookie: foo=bar; bar=baz'); -is $counter, 5, 'right count'; -ok !$req->content->is_parsing_body, 'is not parsing body'; -ok !$req->is_finished, 'request is not finished'; -$req->parse("\x0d\x0a"); -is $counter, 6, 'right count'; -ok !$req->content->is_parsing_body, 'is not parsing body'; -ok !$req->is_finished, 'request is not finished'; -$req->parse("Content-Length: 27\x0d\x0a\x0d\x0aHell"); -is $counter, 7, 'right count'; -ok $req->content->is_parsing_body, 'is parsing body'; -ok !$req->is_finished, 'request is not finished'; -$req->parse("o World!\n1234\nlalalala\n"); -is $counter, 8, 'right count'; -ok !$req->content->is_parsing_body, 'is not parsing body'; -ok $req->is_finished, 'request is finished'; -ok $req->is_finished, 'request is finished'; -is $req->method, 'GET', 'right method'; -is $req->version, '1.0', 'right version'; -is $req->url, '/foo/bar/baz.html?foo=13', 'right URL'; -is $req->headers->content_type, 'text/plain', 'right "Content-Type" value'; -is $req->headers->content_length, 27, 'right "Content-Length" value'; -my $cookies = $req->cookies; -is $cookies->[0]->name, 'foo', 'right name'; -is $cookies->[0]->value, 'bar', 'right value'; -is $cookies->[1]->name, 'bar', 'right name'; -is $cookies->[1]->value, 'baz', 'right value'; - -# Parse and clone multipart/form-data request (changing size) -$req = Mojo::Message::Request->new; -$req->parse("POST /example/testform_handler HTTP/1.1\x0d\x0a"); -$req->parse("User-Agent: Mozilla/5.0\x0d\x0a"); -$req->parse('Content-Type: multipart/form-data; '); -$req->parse("boundary=----WebKitFormBoundaryi5BnD9J9zoTMiSuP\x0d\x0a"); -$req->parse("Content-Length: 318\x0d\x0aConnection: keep-alive\x0d\x0a"); -$req->parse("Host: 127.0.0.1:3000\x0d\x0a\x0d\x0a"); -$req->parse("------WebKitFormBoundaryi5BnD9J9zoTMiSuP\x0d\x0a"); -$req->parse("Content-Disposition: form-data; name=\"Vorname\"\x0a"); -$req->parse("\x0d\x0aT\x0d\x0a------WebKitFormBoundaryi5BnD9J9zoTMiSuP\x0d"); -$req->parse("\x0aContent-Disposition: form-data; name=\"Zuname\"\x0a"); -$req->parse("\x0d\x0a\x0d\x0a------WebKitFormBoundaryi5BnD9J9zoTMiSuP\x0d"); -$req->parse("\x0aContent-Disposition: form-data; name=\"Text\"\x0a"); -$req->parse("\x0d\x0a\x0d\x0a------WebKitFormBoundaryi5BnD9J9zoTMiSuP--"); -ok $req->is_finished, 'request is finished'; -is $req->method, 'POST', 'right method'; -is $req->version, '1.1', 'right version'; -is $req->url, '/example/testform_handler', 'right URL'; -is $req->headers->content_length, 318, 'right "Content-Length" value'; -is $req->param('Vorname'), 'T', 'right value'; -is $req->param('Zuname'), '', 'right value'; -is $req->param('Text'), '', 'right value'; -is $req->content->parts->[0]->asset->slurp, 'T', 'right content'; -is $req->content->leftovers, '', 'no leftovers'; -$req = Mojo::Message::Request->new->parse($req->clone->to_string); -ok $req->is_finished, 'request is finished'; -is $req->method, 'POST', 'right method'; -is $req->version, '1.1', 'right version'; -is $req->url, '/example/testform_handler', 'right URL'; -is $req->headers->content_length, 323, 'right "Content-Length" value'; -is $req->param('Vorname'), 'T', 'right value'; -is $req->param('Zuname'), '', 'right value'; -is $req->param('Text'), '', 'right value'; -is $req->content->parts->[0]->asset->slurp, 'T', 'right content'; -is $req->content->leftovers, '', 'no leftovers'; -is $req->content->get_body_chunk(322), "\x0a", 'right chunk'; -is $req->content->get_body_chunk(321), "\x0d\x0a", 'right chunk'; -is $req->content->get_body_chunk(320), "-\x0d\x0a", 'right chunk'; - -# Parse multipart/form-data request with charset -$req = Mojo::Message::Request->new; -is $req->default_charset, 'UTF-8', 'default charset is UTF-8'; -my $yatta = 'やった'; -my $yatta_sjis = encode 'Shift_JIS', $yatta; -my $multipart - = "------1234567890\x0d\x0a" - . "Content-Disposition: form-data; name=\"$yatta_sjis\"\x0d\x0a\x0d\x0a" - . "$yatta_sjis\x0d\x0a------1234567890--"; -$req->parse("POST /example/yatta HTTP/1.1\x0d\x0a" - . "User-Agent: Mozilla/5.0\x0d\x0a" - . 'Content-Type: multipart/form-data; charset=Shift_JIS; ' - . "boundary=----1234567890\x0d\x0a" - . "Content-Length: @{[length $multipart]}\x0d\x0a" - . "Connection: keep-alive\x0d\x0a" - . "Host: 127.0.0.1:3000\x0d\x0a\x0d\x0a" - . $multipart); -ok $req->is_finished, 'request is finished'; -is $req->method, 'POST', 'right method'; -is $req->version, '1.1', 'right version'; -is $req->url, '/example/yatta', 'right URL'; -is $req->param($yatta), $yatta, 'right value'; -is $req->content->parts->[0]->asset->slurp, $yatta_sjis, 'right content'; - -# WebKit multipart/form-data request -$req = Mojo::Message::Request->new; -$req->parse("POST /example/testform_handler HTTP/1.1\x0d\x0a"); -$req->parse("User-Agent: Mozilla/5.0\x0d\x0a"); -$req->parse('Content-Type: multipart/form-data; '); -$req->parse("boundary=----WebKitFormBoundaryi5BnD9J9zoTMiSuP\x0d\x0a"); -$req->parse("Content-Length: 323\x0d\x0aConnection: keep-alive\x0d\x0a"); -$req->parse("Host: 127.0.0.1:3000\x0d\x0a\x0d\x0a"); -$req->parse("------WebKitFormBoundaryi5BnD9J9zoTMiSuP\x0d\x0a"); -$req->parse("Content-Disposition: form-data; name=\"Vorname\"\x0d\x0a"); -$req->parse("\x0d\x0aT\x0d\x0a------WebKitFormBoundaryi5BnD9J9zoTMiSuP\x0d"); -$req->parse("\x0aContent-Disposition: form-data; name=\"Zuname\"\x0d\x0a"); -$req->parse("\x0d\x0a\x0d\x0a------WebKitFormBoundaryi5BnD9J9zoTMiSuP\x0d"); -$req->parse("\x0aContent-Disposition: form-data; name=\"Text\"\x0d\x0a"); -$req->parse("\x0d\x0a\x0d\x0a------WebKitFormBoundaryi5BnD9J9zoTMiSuP-"); -ok !$req->is_finished, 'request is not finished'; -$req->parse('-'); -ok !$req->is_finished, 'request is not finished'; -$req->parse("\x0d\x0a"); -ok $req->is_finished, 'request is finished'; -is $req->method, 'POST', 'right method'; -is $req->version, '1.1', 'right version'; -is $req->url, '/example/testform_handler', 'right URL'; -is $req->param('Vorname'), 'T', 'right value'; -is $req->param('Zuname'), '', 'right value'; -is $req->param('Text'), '', 'right value'; -is $req->content->parts->[0]->asset->slurp, 'T', 'right content'; - -# Chrome 35 multipart/form-data request (with quotation marks) -$req = Mojo::Message::Request->new; -$req->parse("POST / HTTP/1.1\x0d\x0a"); -$req->parse("Host: 127.0.0.1:3000\x0d\x0a"); -$req->parse("Connection: keep-alive\x0d\x0a"); -$req->parse("Content-Length: 180\x0d\x0a"); -$req->parse('Accept: text/html,application/xhtml+xml,application/xml;q='); -$req->parse("0.9,image/webp,*/*;q=0.8\x0d\x0a"); -$req->parse("Origin: http://127.0.0.1:3000\x0d\x0a"); -$req->parse('User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_0) App'); -$req->parse('leWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.101 Safari'); -$req->parse("/537.36\x0d\x0a"); -$req->parse('Content-Type: multipart/form-data; boundary=----WebKitFormBoun'); -$req->parse("daryMTelhBLWA9N3KXAR\x0d\x0a"); -$req->parse("Referer: http://127.0.0.1:3000/\x0d\x0a"); -$req->parse("Accept-Encoding: gzip,deflate,sdch\x0d\x0a"); -$req->parse("Accept-Language: en-US,en;q=0.8\x0d\x0a\x0d\x0a"); -$req->parse("------WebKitFormBoundaryMTelhBLWA9N3KXAR\x0d\x0a"); -$req->parse('Content-Disposition: form-data; na'); -$req->parse('me="foo \\%22bar%22 baz\"; filename="fo\\%22o%22.txt\"'); -$req->parse("\x0d\x0a\x0d\x0atest\x0d\x0a"); -$req->parse("------WebKitFormBoundaryMTelhBLWA9N3KXAR--\x0d\x0a"); -ok $req->is_finished, 'request is finished'; -is $req->method, 'POST', 'right method'; -is $req->version, '1.1', 'right version'; -is $req->url, '/', 'right URL'; -is $req->upload('foo \\%22bar%22 baz\\')->filename, 'fo\\%22o%22.txt\\', 'right filename'; -is $req->upload('foo \\%22bar%22 baz\\')->slurp, 'test', 'right content'; - -# Firefox 24 multipart/form-data request (with quotation marks) -$req = Mojo::Message::Request->new; -$req->parse("POST / HTTP/1.1\x0d\x0a"); -$req->parse("Host: 127.0.0.1:3000\x0d\x0a"); -$req->parse('User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9; rv:24.'); -$req->parse("0) Gecko/20100101 Firefox/24.0\x0d\x0a"); -$req->parse('Accept: text/html,application/xhtml+xml,application/xml;q=0.9'); -$req->parse(",*/*;q=0.8\x0d\x0a"); -$req->parse("Accept-Language: de-de,de;q=0.8,en-us;q=0.5,en;q=0.3\x0d\x0a"); -$req->parse("Accept-Encoding: gzip, deflate\x0d\x0a"); -$req->parse("Referer: http://127.0.0.1:3000/\x0d\x0a"); -$req->parse("Connection: keep-alive\x0d\x0a"); -$req->parse('Content-Type: multipart/form-data; boundary=-----------------'); -$req->parse("----------20773201241877674789807986058\x0d\x0a"); -$req->parse("Content-Length: 212\x0d\x0a\x0d\x0a"); -$req->parse('-----------------------------2077320124187767478980'); -$req->parse("7986058\x0d\x0aContent-Disposition: form-data; na"); -$req->parse('me="foo \\\\"bar\\" baz"; filename="fo\\\\"o\\".txt"'); -$req->parse("\x0d\x0a\x0d\x0atest\x0d\x0a"); -$req->parse('-----------------------------2077320124187767'); -$req->parse("4789807986058--\x0d\x0a"); -ok $req->is_finished, 'request is finished'; -is $req->method, 'POST', 'right method'; -is $req->version, '1.1', 'right version'; -is $req->url, '/', 'right URL'; -is $req->upload('foo \\\"bar\" baz')->filename, 'fo\\\\"o\\".txt', 'right filename'; -is $req->upload('foo \\\"bar\" baz')->slurp, 'test', 'right content'; - -# Chrome 5 multipart/form-data request (UTF-8) -$req = Mojo::Message::Request->new; -my ($fname, $sname, $sex, $avatar, $submit) = map { encode 'UTF-8', $_ } 'Иван', 'Иванов', 'мужской', 'аватар.jpg', - 'Сохранить'; -my $chrome - = "------WebKitFormBoundaryYGjwdkpB6ZLCZQbX\x0d\x0a" - . "Content-Disposition: form-data; name=\"fname\"\x0d\x0a\x0d\x0a" - . "$fname\x0d\x0a------WebKitFormBoundaryYGjwdkpB6ZLCZQbX\x0d\x0a" - . "Content-Disposition: form-data; name=\"sname\"\x0d\x0a\x0d\x0a" - . "$sname\x0d\x0a------WebKitFormBoundaryYGjwdkpB6ZLCZQbX\x0d\x0a" - . "Content-Disposition: form-data; name=\"sex\"\x0d\x0a\x0d\x0a" - . "$sex\x0d\x0a------WebKitFormBoundaryYGjwdkpB6ZLCZQbX\x0d\x0a" - . "Content-Disposition: form-data; name=\"bdate\"\x0d\x0a\x0d\x0a" - . "16.02.1987\x0d\x0a------WebKitFormBoundaryYGjwdkpB6ZLCZQbX\x0d\x0a" - . "Content-Disposition: form-data; name=\"phone\"\x0d\x0a\x0d\x0a" - . "1234567890\x0d\x0a------WebKitFormBoundaryYGjwdkpB6ZLCZQbX\x0d\x0a" - . "Content-Disposition: form-data; name=\"avatar\"; filename=\"$avatar\"" - . "\x0d\x0aContent-Type: image/jpeg\x0d\x0a\x0d\x0a1234" - . "\x0d\x0a------WebKitFormBoundaryYGjwdkpB6ZLCZQbX\x0d\x0a" - . "Content-Disposition: form-data; name=\"submit\"\x0d\x0a\x0d\x0a" - . "$submit\x0d\x0a------WebKitFormBoundaryYGjwdkpB6ZLCZQbX--"; -$req->parse("POST / HTTP/1.0\x0d\x0a" - . "Host: 127.0.0.1:10002\x0d\x0a" - . "Connection: close\x0d\x0a" - . 'User-Agent: Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/5' - . "32.9 (KHTML, like Gecko) Chrome/5.0.307.11 Safari/532.9\x0d\x0a" - . "Referer: http://example.org/\x0d\x0a" - . "Content-Length: @{[length $chrome]}\x0d\x0a" - . "Cache-Control: max-age=0\x0d\x0a" - . "Origin: http://example.org\x0d\x0a" - . 'Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryY' - . "GjwdkpB6ZLCZQbX\x0d\x0a" - . 'Accept: application/xml,application/xhtml+xml,text/html;q=0.9,text/' - . "plain;q=0.8,image/png,*/*;q=0.5\x0d\x0a" - . "Accept-Encoding: gzip,deflate,sdch\x0d\x0a" - . 'Cookie: mojolicious=BAcIMTIzNDU2NzgECAgIAwIAAAAXDGFsZXgudm9yb25vdgQ' - . 'AAAB1c2VyBp6FjksAAAAABwAAAGV4cGlyZXM=--1641adddfe885276cda0deb7475f' - . "153a\x0d\x0a" - . "Accept-Language: ru-RU,ru;q=0.8,en-US;q=0.6,en;q=0.4\x0d\x0a" - . "Accept-Charset: windows-1251,utf-8;q=0.7,*;q=0.3\x0d\x0a\x0d\x0a" - . $chrome); -ok $req->is_finished, 'request is finished'; -is $req->method, 'POST', 'right method'; -is $req->version, '1.0', 'right version'; -is $req->url, '/', 'right URL'; -is $req->cookie('mojolicious')->value, 'BAcIMTIzNDU2NzgECAgIAwIAAAAXDGFsZXgudm9yb25vdgQAAAB1c2VyBp6FjksAAAAABwA' - . 'AAGV4cGlyZXM=--1641adddfe885276cda0deb7475f153a', 'right value'; -like $req->headers->content_type, qr!multipart/form-data!, 'right "Content-Type" value'; -is $req->param('fname'), 'Иван', 'right value'; -is $req->param('sname'), 'Иванов', 'right value'; -is $req->param('sex'), 'мужской', 'right value'; -is $req->param('bdate'), '16.02.1987', 'right value'; -is $req->param('phone'), '1234567890', 'right value'; -is $req->param('submit'), 'Сохранить', 'right value'; -my $upload = $req->upload('avatar'); -is $upload->isa('Mojo::Upload'), 1, 'right upload'; -is $upload->headers->content_type, 'image/jpeg', 'right "Content-Type" value'; -is $upload->filename, 'аватар.jpg', 'right filename'; -is $upload->size, 4, 'right size'; -is $upload->slurp, '1234', 'right content'; -is $req->content->parts->[0]->asset->slurp, $fname, 'right content'; - -# Firefox 3.5.8 multipart/form-data request (UTF-8) -$req = Mojo::Message::Request->new; -($fname, $sname, $sex, $avatar, $submit) = map { encode 'UTF-8', $_ } 'Иван', 'Иванов', 'мужской', 'аватар.jpg', - 'Сохранить'; -my $firefox - = "-----------------------------213090722714721300002030499922\x0d\x0a" - . "Content-Disposition: form-data; name=\"fname\"\x0d\x0a\x0d\x0a" - . "$fname\x0d\x0a" - . "-----------------------------213090722714721300002030499922\x0d\x0a" - . "Content-Disposition: form-data; name=\"sname\"\x0d\x0a\x0d\x0a" - . "$sname\x0d\x0a" - . "-----------------------------213090722714721300002030499922\x0d\x0a" - . "Content-Disposition: form-data; name=\"sex\"\x0d\x0a\x0d\x0a" - . "$sex\x0d\x0a" - . "-----------------------------213090722714721300002030499922\x0d\x0a" - . "Content-Disposition: form-data; name=\"bdate\"\x0d\x0a\x0d\x0a" - . "16.02.1987\x0d\x0a" - . "-----------------------------213090722714721300002030499922\x0d\x0a" - . "Content-Disposition: form-data; name=\"phone\"\x0d\x0a\x0d\x0a" - . "1234567890\x0d\x0a" - . "-----------------------------213090722714721300002030499922\x0d\x0a" - . "Content-Disposition: form-data; name=\"avatar\"; filename=\"$avatar\"" - . "\x0d\x0aContent-Type: image/jpeg\x0d\x0a\x0d\x0a1234\x0d\x0a" - . "-----------------------------213090722714721300002030499922\x0d\x0a" - . "Content-Disposition: form-data; name=\"submit\"\x0d\x0a\x0d\x0a" - . "$submit\x0d\x0a" - . '-----------------------------213090722714721300002030499922--'; -$req->parse("POST / HTTP/1.0\x0d\x0a" - . "Host: 127.0.0.1:10002\x0d\x0a" - . "Connection: close\x0d\x0a" - . 'User-Agent: Mozilla/5.0 (X11; U; Linux x86_64; ru; rv:1.9.1.8) Geck' - . "o/20100214 Ubuntu/9.10 (karmic) Firefox/3.5.8\x0d\x0a" - . 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q' - . "=0.8\x0d\x0a" - . "Accept-Language: ru,en-us;q=0.7,en;q=0.3\x0d\x0a" - . "Accept-Encoding: gzip,deflate\x0d\x0a" - . "Accept-Charset: windows-1251,utf-8;q=0.7,*;q=0.7\x0d\x0a" - . "Referer: http://example.org/\x0d\x0a" - . 'Cookie: mojolicious=BAcIMTIzNDU2NzgECAgIAwIAAAAXDGFsZXgudm9yb25vdgQ' - . 'AAAB1c2VyBiWFjksAAAAABwAAAGV4cGlyZXM=--cd933a37999e0fa8d7804205e891' - . "93a7\x0d\x0a" - . 'Content-Type: multipart/form-data; boundary=-----------------------' - . "----213090722714721300002030499922\x0d\x0a" - . "Content-Length: @{[length $firefox]}\x0d\x0a\x0d\x0a" - . $firefox); -ok $req->is_finished, 'request is finished'; -is $req->method, 'POST', 'right method'; -is $req->version, '1.0', 'right version'; -is $req->url, '/', 'right URL'; -is $req->cookie('mojolicious')->value, 'BAcIMTIzNDU2NzgECAgIAwIAAAAXDGFsZXgudm9yb25vdgQAAAB1c2VyBiWFjksAAAAABwA' - . 'AAGV4cGlyZXM=--cd933a37999e0fa8d7804205e89193a7', 'right value'; -like $req->headers->content_type, qr!multipart/form-data!, 'right "Content-Type" value'; -is $req->param('fname'), 'Иван', 'right value'; -is $req->param('sname'), 'Иванов', 'right value'; -is $req->param('sex'), 'мужской', 'right value'; -is $req->param('bdate'), '16.02.1987', 'right value'; -is $req->param('phone'), '1234567890', 'right value'; -is $req->param('submit'), 'Сохранить', 'right value'; -$upload = $req->upload('avatar'); -is $upload->isa('Mojo::Upload'), 1, 'right upload'; -is $upload->headers->content_type, 'image/jpeg', 'right "Content-Type" value'; -is $upload->filename, 'аватар.jpg', 'right filename'; -is $upload->size, 4, 'right size'; -is $upload->slurp, '1234', 'right content'; -is $req->content->parts->[0]->asset->slurp, $fname, 'right content'; - -# Opera 9.8 multipart/form-data request (UTF-8) -$req = Mojo::Message::Request->new; -($fname, $sname, $sex, $avatar, $submit) = map { encode 'UTF-8', $_ } 'Иван', 'Иванов', 'мужской', 'аватар.jpg', - 'Сохранить'; -my $opera - = "------------IWq9cR9mYYG668xwSn56f0\x0d\x0a" - . "Content-Disposition: form-data; name=\"fname\"\x0d\x0a\x0d\x0a" - . "$fname\x0d\x0a------------IWq9cR9mYYG668xwSn56f0\x0d\x0a" - . "Content-Disposition: form-data; name=\"sname\"\x0d\x0a\x0d\x0a" - . "$sname\x0d\x0a------------IWq9cR9mYYG668xwSn56f0\x0d\x0a" - . "Content-Disposition: form-data; name=\"sex\"\x0d\x0a\x0d\x0a" - . "$sex\x0d\x0a------------IWq9cR9mYYG668xwSn56f0\x0d\x0a" - . "Content-Disposition: form-data; name=\"bdate\"\x0d\x0a\x0d\x0a" - . "16.02.1987\x0d\x0a------------IWq9cR9mYYG668xwSn56f0\x0d\x0a" - . "Content-Disposition: form-data; name=\"phone\"\x0d\x0a\x0d\x0a" - . "1234567890\x0d\x0a------------IWq9cR9mYYG668xwSn56f0\x0d\x0a" - . "Content-Disposition: form-data; name=\"avatar\"; filename=\"$avatar\"" - . "\x0d\x0aContent-Type: image/jpeg\x0d\x0a\x0d\x0a" - . "1234\x0d\x0a------------IWq9cR9mYYG668xwSn56f0\x0d\x0a" - . "Content-Disposition: form-data; name=\"submit\"\x0d\x0a\x0d\x0a" - . "$submit\x0d\x0a------------IWq9cR9mYYG668xwSn56f0--"; -$req->parse("POST / HTTP/1.0\x0d\x0a" - . "Host: 127.0.0.1:10002\x0d\x0a" - . "Connection: close\x0d\x0a" - . 'User-Agent: Opera/9.80 (X11; Linux x86_64; U; ru) Presto/2.2.15 Ver' - . "sion/10.10\x0d\x0a" - . 'Accept: text/html, application/xml;q=0.9, application/xhtml+xml, im' - . "age/png, image/jpeg, image/gif, image/x-xbitmap, */*;q=0.1\x0d\x0a" - . "Accept-Language: ru-RU,ru;q=0.9,en;q=0.8\x0d\x0a" - . "Accept-Charset: iso-8859-1, utf-8, utf-16, *;q=0.1\x0d\x0a" - . "Accept-Encoding: deflate, gzip, x-gzip, identity, *;q=0\x0d\x0a" - . "Referer: http://example.org/\x0d\x0a" - . 'Cookie: mojolicious=BAcIMTIzNDU2NzgECAgIAwIAAAAXDGFsZXgudm9yb25vdgQ' - . 'AAAB1c2VyBhaIjksAAAAABwAAAGV4cGlyZXM=--78a58a94f98ae5b75a489be1189f' - . "2672\x0d\x0a" - . "Cookie2: \$Version=1\x0d\x0a" - . "TE: deflate, gzip, chunked, identity, trailers\x0d\x0a" - . "Content-Length: @{[length $opera]}\x0d\x0a" - . 'Content-Type: multipart/form-data; boundary=----------IWq9cR9mYYG66' - . "8xwSn56f0\x0d\x0a\x0d\x0a" - . $opera); -ok $req->is_finished, 'request is finished'; -is $req->method, 'POST', 'right method'; -is $req->version, '1.0', 'right version'; -is $req->url, '/', 'right URL'; -is $req->cookie('mojolicious')->value, 'BAcIMTIzNDU2NzgECAgIAwIAAAAXDGFsZXgudm9yb25vdgQAAAB1c2VyBhaIjksAAAAABwA' - . 'AAGV4cGlyZXM=--78a58a94f98ae5b75a489be1189f2672', 'right value'; -like $req->headers->content_type, qr!multipart/form-data!, 'right "Content-Type" value'; -is $req->param('fname'), 'Иван', 'right value'; -is $req->param('sname'), 'Иванов', 'right value'; -is $req->param('sex'), 'мужской', 'right value'; -is $req->param('bdate'), '16.02.1987', 'right value'; -is $req->param('phone'), '1234567890', 'right value'; -is $req->param('submit'), 'Сохранить', 'right value'; -$upload = $req->upload('avatar'); -is $upload->isa('Mojo::Upload'), 1, 'right upload'; -is $upload->headers->content_type, 'image/jpeg', 'right "Content-Type" value'; -is $upload->filename, 'аватар.jpg', 'right filename'; -is $upload->size, 4, 'right size'; -is $upload->slurp, '1234', 'right content'; -is $req->content->parts->[0]->asset->slurp, $fname, 'right content'; - -# Firefox 14 multipart/form-data request (UTF-8) -$req = Mojo::Message::Request->new; -my ($name, $filename) = map { encode 'UTF-8', $_ } '☃', 'foo bär ☃.txt'; -$firefox - = "-----------------------------1264369278903768281481663536\x0d\x0a" - . "Content-Disposition: form-data; name=\"$name\"; filename=\"$filename\"" - . "\x0d\x0aContent-Type: text/plain\x0d\x0a\x0d\x0atest 123\x0d\x0a" - . '-----------------------------1264369278903768281481663536--'; -$req->parse("POST /foo HTTP/1.1\x0d\x0a"); -$req->parse("Host: 127.0.0.1:3000\x0d\x0a"); -$req->parse('User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv'); -$req->parse(":14.0) Gecko/20100101 Firefox/14.0.1\x0d\x0a"); -$req->parse('Accept: text/html,application/xhtml+xml,application/xml;'); -$req->parse("q=0.9,*/*;q=0.8\x0d\x0a"); -$req->parse("Accept-Language: en-us,en;q=0.5\x0d\x0a"); -$req->parse("Accept-Encoding: gzip, deflate\x0d\x0a"); -$req->parse("Connection: keep-alive\x0d\x0a"); -$req->parse("Referer: http://127.0.0.1:3000/\x0d\x0a"); -$req->parse('Content-Type: multipart/form-data;'); -$req->parse('boundary=---------------------------126436927890376828148'); -$req->parse("1663536\x0d\x0a"); -$req->parse("Content-Length: @{[length $firefox]}\x0d\x0a\x0d\x0a"); -$req->parse($firefox); -ok $req->is_finished, 'request is finished'; -is $req->method, 'POST', 'right method'; -is $req->version, '1.1', 'right version'; -is $req->url, '/foo', 'right URL'; -like $req->headers->content_type, qr!multipart/form-data!, 'right "Content-Type" value'; -is $req->upload('☃')->name, '☃', 'right name'; -is $req->upload('☃')->filename, 'foo bär ☃.txt', 'right filename'; -is $req->upload('☃')->headers->content_type, 'text/plain', 'right "Content-Type" value'; -is $req->upload('☃')->asset->size, 8, 'right size'; -is $req->upload('☃')->asset->slurp, 'test 123', 'right content'; - -# Parse "~" in URL -$req = Mojo::Message::Request->new; -$req->parse("GET /~foobar/ HTTP/1.1\x0d\x0a\x0d\x0a"); -ok $req->is_finished, 'request is finished'; -is $req->method, 'GET', 'right method'; -is $req->version, '1.1', 'right version'; -is $req->url, '/~foobar/', 'right URL'; - -# Parse ":" in URL -$req = Mojo::Message::Request->new; -$req->parse("GET /perldoc?Mojo::Message::Request HTTP/1.1\x0d\x0a\x0d\x0a"); -ok $req->is_finished, 'request is finished'; -is $req->method, 'GET', 'right method'; -is $req->version, '1.1', 'right version'; -is $req->url, '/perldoc?Mojo::Message::Request', 'right URL'; -is $req->url->query->pairs->[0], 'Mojo::Message::Request', 'right value'; - -# Parse lots of special characters in URL -$req = Mojo::Message::Request->new; -$req->parse("GET /09azAZ!\$%&'()*+,-./:;=?@[\\]^_`{|}~\xC3\x9F#abc "); -$req->parse("HTTP/1.1\x0d\x0a\x0d\x0a"); -ok $req->is_finished, 'request is finished'; -is $req->method, 'GET', 'right method'; -is $req->version, '1.1', 'right version'; -is $req->url, "/09azAZ!\$%&'()*+,-./:;=?@%5B%5C%5D%5E_%60%7B%7C%7D~%C3%83%C2%9F", 'right URL'; - -# Abstract methods -eval { Mojo::Message->cookies }; -like $@, qr/Method "cookies" not implemented by subclass/, 'right error'; -eval { Mojo::Message->extract_start_line }; -like $@, qr/Method "extract_start_line" not implemented by subclass/, 'right error'; -eval { Mojo::Message->get_start_line_chunk }; -like $@, qr/Method "get_start_line_chunk" not implemented by subclass/, 'right error'; -eval { Mojo::Message->start_line_size }; -like $@, qr/Method "start_line_size" not implemented by subclass/, 'right error'; +}; + +subtest 'Parse full HTTP 1.0 request (solitary LF)' => sub { + my $req = Mojo::Message::Request->new; + my $body = ''; + $req->content->on(read => sub { $body .= pop }); + $req->parse('GET /foo/bar/baz.html?fo'); + $req->parse("o=13 HTTP/1.0\x0aContent"); + $req->parse('-Type: text/'); + $req->parse("plain\x0aContent-Length: 27\x0a\x0aH"); + $req->parse("ello World!\n1234\nlalalala\n"); + ok $req->is_finished, 'request is finished'; + is $req->method, 'GET', 'right method'; + is $req->version, '1.0', 'right version'; + is $req->url, '/foo/bar/baz.html?foo=13', 'right URL'; + is $req->headers->content_type, 'text/plain', 'right "Content-Type" value'; + is $req->headers->content_length, 27, 'right "Content-Length" value'; + is $req->body, "Hello World!\n1234\nlalalala\n", 'right content'; + is $body, "Hello World!\n1234\nlalalala\n", 'right content'; +}; + +subtest 'Parse full HTTP 1.0 request (no scheme and empty elements in path)' => sub { + my $req = Mojo::Message::Request->new; + $req->parse('GET //foo/bar//baz.html?fo'); + $req->parse("o=13 HTTP/1.0\x0d\x0aContent"); + $req->parse('-Type: text/'); + $req->parse("plain\x0d\x0aContent-Length: 27\x0d\x0a\x0d\x0aHell"); + $req->parse("o World!\n1234\nlalalala\n"); + ok $req->is_finished, 'request is finished'; + is $req->method, 'GET', 'right method'; + is $req->version, '1.0', 'right version'; + is $req->url->host, undef, 'no host'; + is $req->url->path, '//foo/bar//baz.html', 'right path'; + is $req->url, '//foo/bar//baz.html?foo=13', 'right URL'; + is $req->headers->content_type, 'text/plain', 'right "Content-Type" value'; + is $req->headers->content_length, 27, 'right "Content-Length" value'; +}; + +subtest 'Parse full HTTP 1.0 request (behind reverse proxy)' => sub { + my $req = Mojo::Message::Request->new; + $req->parse('GET /foo/bar/baz.html?fo'); + $req->parse("o=13 HTTP/1.0\x0d\x0aContent"); + $req->parse('-Type: text/'); + $req->parse("plain\x0d\x0aContent-Length: 27\x0d\x0a"); + $req->parse("Host: mojolicious.org\x0d\x0a"); + $req->parse("X-Forwarded-For: 192.168.2.1, 127.0.0.1\x0d\x0a\x0d\x0a"); + $req->parse("Hello World!\n1234\nlalalala\n"); + ok $req->is_finished, 'request is finished'; + is $req->method, 'GET', 'right method'; + is $req->version, '1.0', 'right version'; + is $req->url, '/foo/bar/baz.html?foo=13', 'right URL'; + is $req->url->to_abs, 'http://mojolicious.org/foo/bar/baz.html?foo=13', 'right absolute URL'; + is $req->headers->content_type, 'text/plain', 'right "Content-Type" value'; + is $req->headers->content_length, 27, 'right "Content-Length" value'; +}; + +subtest 'Parse full HTTP 1.0 request with zero chunk' => sub { + my $req = Mojo::Message::Request->new; + my $finished = undef; + $req->on(finish => sub { $finished = shift->is_finished }); + $req->parse('GET /foo/bar/baz.html?fo'); + $req->parse("o=13 HTTP/1.0\x0d\x0aContent"); + $req->parse('-Type: text/'); + $req->parse("plain\x0d\x0aContent-Length: 27\x0d\x0a\x0d\x0aHell"); + $req->parse("o World!\n123"); + $req->parse('0'); + $req->parse("\nlalalala\n"); + ok $finished, 'finish event has been emitted'; + ok $req->is_finished, 'request is finished'; + is $req->method, 'GET', 'right method'; + is $req->version, '1.0', 'right version'; + is $req->url, '/foo/bar/baz.html?foo=13', 'right URL'; + is $req->headers->content_type, 'text/plain', 'right "Content-Type" value'; + is $req->headers->content_length, 27, 'right "Content-Length" value'; +}; + +subtest 'Parse full HTTP 1.0 request with UTF-8 form input' => sub { + my $req = Mojo::Message::Request->new; + $req->parse('GET /foo/bar/baz.html?fo'); + $req->parse("o=13 HTTP/1.0\x0d\x0aContent"); + $req->parse('-Type: application/'); + $req->parse("x-www-form-urlencoded\x0d\x0aContent-Length: 14"); + $req->parse("\x0d\x0a\x0d\x0a"); + $req->parse('name=%E2%98%83'); + ok $req->is_finished, 'request is finished'; + is $req->method, 'GET', 'right method'; + is $req->version, '1.0', 'right version'; + is $req->url, '/foo/bar/baz.html?foo=13', 'right URL'; + is $req->headers->content_type, 'application/x-www-form-urlencoded', 'right "Content-Type" value'; + is $req->headers->content_length, 14, 'right "Content-Length" value'; + is $req->param('name'), '☃', 'right value'; +}; + +subtest 'Parse HTTP 1.1 gzip compressed request (no decompression)' => sub { + my $compressed = gzip my $uncompressed = 'abc' x 1000; + my $req = Mojo::Message::Request->new; + $req->parse("POST /foo HTTP/1.1\x0d\x0a"); + $req->parse("Content-Type: text/plain\x0d\x0a"); + $req->parse("Content-Length: @{[length $compressed]}\x0d\x0a"); + $req->parse("Content-Encoding: GZip\x0d\x0a\x0d\x0a"); + ok $req->content->is_compressed, 'content is compressed'; + $req->parse($compressed); + ok $req->content->is_compressed, 'content is still compressed'; + ok $req->is_finished, 'request is finished'; + is $req->method, 'POST', 'right method'; + is $req->version, '1.1', 'right version'; + is $req->url, '/foo', 'right URL'; + is $req->headers->content_type, 'text/plain', 'right "Content-Type" value'; + is $req->headers->content_length, length($compressed), 'right "Content-Length" value'; + is $req->body, $compressed, 'right content'; +}; + +subtest 'Parse HTTP 1.1 chunked request' => sub { + my $req = Mojo::Message::Request->new; + is $req->content->progress, 0, 'right progress'; + $req->parse("POST /foo/bar/baz.html?foo=13 HTTP/1.1\x0d\x0a"); + is $req->content->progress, 0, 'right progress'; + $req->parse("Content-Type: text/plain\x0d\x0a"); + $req->parse("Transfer-Encoding: chunked\x0d\x0a\x0d\x0a"); + is $req->content->progress, 0, 'right progress'; + $req->parse("4\x0d\x0a"); + is $req->content->progress, 3, 'right progress'; + $req->parse("abcd\x0d\x0a"); + is $req->content->progress, 9, 'right progress'; + $req->parse("9\x0d\x0a"); + is $req->content->progress, 12, 'right progress'; + $req->parse("abcdefghi\x0d\x0a"); + is $req->content->progress, 23, 'right progress'; + $req->parse("0\x0d\x0a\x0d\x0a"); + is $req->content->progress, 28, 'right progress'; + ok $req->is_finished, 'request is finished'; + is $req->method, 'POST', 'right method'; + is $req->version, '1.1', 'right version'; + is $req->url, '/foo/bar/baz.html?foo=13', 'right URL'; + is $req->headers->content_length, 13, 'right "Content-Length" value'; + is $req->headers->content_type, 'text/plain', 'right "Content-Type" value'; + is $req->content->asset->size, 13, 'right size'; + is $req->content->asset->slurp, 'abcdabcdefghi', 'right content'; +}; + +subtest 'Parse HTTP 1.1 chunked request with callbacks' => sub { + my $req = Mojo::Message::Request->new; + my $progress = my $buffer = my $finish = ''; + $req->on( + progress => sub { + my $self = shift; + $progress ||= $self->url->path if $self->content->is_parsing_body; + } + ); + $req->content->unsubscribe('read')->on(read => sub { $buffer .= pop }); + $req->on(finish => sub { $finish .= shift->url->query }); + $req->parse("POST /foo/bar/baz.html?foo=13 HTTP/1.1\x0d\x0a"); + is $progress, '', 'no progress'; + $req->parse("Content-Type: text/plain\x0d\x0a"); + is $progress, '', 'no progress'; + $req->parse("Transfer-Encoding: chunked\x0d\x0a\x0d\x0a"); + is $progress, '/foo/bar/baz.html', 'made progress'; + $req->parse("4\x0d\x0a"); + $req->parse("abcd\x0d\x0a"); + $req->parse("9\x0d\x0a"); + $req->parse("abcdefghi\x0d\x0a"); + is $finish, '', 'not finished yet'; + $req->parse("0\x0d\x0a\x0d\x0a"); + is $finish, 'foo=13', 'finished'; + is $progress, '/foo/bar/baz.html', 'made progress'; + ok $req->is_finished, 'request is finished'; + is $req->method, 'POST', 'right method'; + is $req->version, '1.1', 'right version'; + is $req->url, '/foo/bar/baz.html?foo=13', 'right URL'; + is $req->headers->content_length, 13, 'right "Content-Length" value'; + is $req->headers->content_type, 'text/plain', 'right "Content-Type" value'; + is $buffer, 'abcdabcdefghi', 'right content'; +}; + +subtest 'Parse HTTP 1.1 "application/x-www-form-urlencoded"' => sub { + my $req = Mojo::Message::Request->new; + $req->parse("POST /foo/bar/baz.html?foo=13 HTTP/1.1\x0d\x0a"); + $req->parse("Content-Length: 25\x0d\x0a"); + $req->parse("Content-Type: application/x-www-form-urlencoded\x0d\x0a"); + $req->parse("\x0d\x0afoo=bar&+tset=23+&foo=bar"); + ok $req->is_finished, 'request is finished'; + is $req->method, 'POST', 'right method'; + is $req->version, '1.1', 'right version'; + is $req->url, '/foo/bar/baz.html?foo=13', 'right URL'; + is $req->headers->content_type, 'application/x-www-form-urlencoded', 'right "Content-Type" value'; + is $req->content->asset->size, 25, 'right size'; + is $req->content->asset->slurp, 'foo=bar&+tset=23+&foo=bar', 'right content'; + is_deeply $req->body_params->to_hash->{foo}, [qw(bar bar)], 'right values'; + is $req->body_params->to_hash->{' tset'}, '23 ', 'right value'; + is $req->body_params, 'foo=bar&+tset=23+&foo=bar', 'right parameters'; + is_deeply $req->params->to_hash->{foo}, [qw(bar bar 13)], 'right values'; + is_deeply $req->every_param('foo'), [qw(bar bar 13)], 'right values'; + is $req->param(' tset'), '23 ', 'right value'; + $req->param('set', 'single'); + is $req->param('set'), 'single', 'setting single param works'; + $req->param('multi', 1, 2, 3); + is_deeply $req->every_param('multi'), [qw(1 2 3)], 'setting multiple value param works'; + is $req->param('test23'), undef, 'no value'; +}; + +subtest 'Parse HTTP 1.1 chunked request with trailing headers' => sub { + my $req = Mojo::Message::Request->new; + $req->parse("POST /foo/bar/baz.html?foo=13&bar=23 HTTP/1.1\x0d\x0a"); + $req->parse("Content-Type: text/plain\x0d\x0a"); + $req->parse("Transfer-Encoding: whatever\x0d\x0a"); + $req->parse("Trailer: X-Trailer1; X-Trailer2\x0d\x0a\x0d\x0a"); + $req->parse("4\x0d\x0a"); + $req->parse("abcd\x0d\x0a"); + $req->parse("9\x0d\x0a"); + $req->parse("abcdefghi\x0d\x0a"); + $req->parse("0\x0d\x0a"); + $req->parse("X-Trailer1: test\x0d\x0a"); + $req->parse("X-Trailer2: 123\x0d\x0a\x0d\x0a"); + ok $req->is_finished, 'request is finished'; + is $req->method, 'POST', 'right method'; + is $req->version, '1.1', 'right version'; + is $req->url, '/foo/bar/baz.html?foo=13&bar=23', 'right URL'; + is $req->query_params, 'foo=13&bar=23', 'right parameters'; + is $req->headers->content_type, 'text/plain', 'right "Content-Type" value'; + is $req->headers->header('X-Trailer1'), 'test', 'right "X-Trailer1" value'; + is $req->headers->header('X-Trailer2'), '123', 'right "X-Trailer2" value'; + is $req->headers->content_length, 13, 'right "Content-Length" value'; + is $req->content->asset->size, 13, 'right size'; + is $req->content->asset->slurp, 'abcdabcdefghi', 'right content'; +}; + +subtest 'Parse HTTP 1.1 chunked request with trailing headers (different variation)' => sub { + my $req = Mojo::Message::Request->new; + $req->parse("POST /foo/bar/baz.html?foo=13&bar=23 HTTP/1.1\x0d\x0a"); + $req->parse("Content-Type: text/plain\x0d\x0aTransfer-Enc"); + $req->parse("oding: chunked\x0d\x0a"); + $req->parse("Trailer: X-Trailer\x0d\x0a\x0d\x0a"); + $req->parse("4\x0d\x0a"); + $req->parse("abcd\x0d\x0a"); + $req->parse("9\x0d\x0a"); + $req->parse("abcdefghi\x0d\x0a"); + $req->parse("0\x0d\x0aX-Trailer: 777\x0d\x0a\x0d\x0aLEFTOVER"); + ok $req->is_finished, 'request is finished'; + is $req->method, 'POST', 'right method'; + is $req->version, '1.1', 'right version'; + is $req->url, '/foo/bar/baz.html?foo=13&bar=23', 'right URL'; + is $req->query_params, 'foo=13&bar=23', 'right parameters'; + ok !defined $req->headers->transfer_encoding, 'no "Transfer-Encoding" value'; + is $req->headers->content_type, 'text/plain', 'right "Content-Type" value'; + is $req->headers->header('X-Trailer'), '777', 'right "X-Trailer" value'; + is $req->headers->content_length, 13, 'right "Content-Length" value'; + is $req->content->asset->size, 13, 'right size'; + is $req->content->asset->slurp, 'abcdabcdefghi', 'right content'; +}; + +subtest 'Parse HTTP 1.1 chunked request with trailing headers (different variation)' => sub { + my $req = Mojo::Message::Request->new; + $req->parse("POST /foo/bar/baz.html?foo=13&bar=23 HTTP/1.1\x0d\x0a"); + $req->parse("Content-Type: text/plain\x0d\x0a"); + $req->parse("Transfer-Encoding: chunked\x0d\x0a"); + $req->parse("Trailer: X-Trailer1; X-Trailer2\x0d\x0a\x0d\x0a"); + $req->parse("4\x0d\x0a"); + $req->parse("abcd\x0d\x0a"); + $req->parse("9\x0d\x0a"); + $req->parse("abcdefghi\x0d\x0a"); + $req->parse("0\x0d\x0aX-Trailer1: test\x0d\x0aX-Trailer2: 123\x0d\x0a\x0d\x0a"); + ok $req->is_finished, 'request is finished'; + is $req->method, 'POST', 'right method'; + is $req->version, '1.1', 'right version'; + is $req->url, '/foo/bar/baz.html?foo=13&bar=23', 'right URL'; + is $req->query_params, 'foo=13&bar=23', 'right parameters'; + is $req->headers->content_type, 'text/plain', 'right "Content-Type" value'; + is $req->headers->header('X-Trailer1'), 'test', 'right "X-Trailer1" value'; + is $req->headers->header('X-Trailer2'), '123', 'right "X-Trailer2" value'; + is $req->headers->content_length, 13, 'right "Content-Length" value'; + is $req->content->asset->size, 13, 'right size'; + is $req->content->asset->slurp, 'abcdabcdefghi', 'right content'; +}; + +subtest 'Parse HTTP 1.1 chunked request with trailing headers (no Trailer header)' => sub { + my $req = Mojo::Message::Request->new; + $req->parse("POST /foo/bar/baz.html?foo=13&bar=23 HTTP/1.1\x0d\x0a"); + $req->parse("Content-Type: text/plain\x0d\x0a"); + $req->parse("Transfer-Encoding: chunked\x0d\x0a\x0d\x0a"); + $req->parse("4\x0d\x0a"); + $req->parse("abcd\x0d\x0a"); + $req->parse("9\x0d\x0a"); + $req->parse("abcdefghi\x0d\x0a"); + $req->parse("0\x0d\x0aX-Trailer1: test\x0d\x0aX-Trailer2: 123\x0d\x0a\x0d\x0a"); + ok $req->is_finished, 'request is finished'; + is $req->method, 'POST', 'right method'; + is $req->version, '1.1', 'right version'; + is $req->url, '/foo/bar/baz.html?foo=13&bar=23', 'right URL'; + is $req->query_params, 'foo=13&bar=23', 'right parameters'; + is $req->headers->content_type, 'text/plain', 'right "Content-Type" value'; + is $req->headers->header('X-Trailer1'), 'test', 'right "X-Trailer1" value'; + is $req->headers->header('X-Trailer2'), '123', 'right "X-Trailer2" value'; + is $req->headers->content_length, 13, 'right "Content-Length" value'; + is $req->content->asset->size, 13, 'right size'; + is $req->content->asset->slurp, 'abcdabcdefghi', 'right content'; +}; + +subtest 'Parse HTTP 1.1 multipart request' => sub { + my $req = Mojo::Message::Request->new; + is $req->content->progress, 0, 'right progress'; + $req->parse("GET /foo/bar/baz.html?foo13 HTTP/1.1\x0d\x0a"); + is $req->content->progress, 0, 'right progress'; + $req->parse("Content-Length: 416\x0d\x0a"); + $req->parse('Content-Type: multipart/form-data; bo'); + is $req->content->progress, 0, 'right progress'; + $req->parse("undary=----------0xKhTmLbOuNdArY\x0d\x0a\x0d\x0a"); + is $req->content->progress, 0, 'right progress'; + $req->parse("\x0d\x0a------------0xKhTmLbOuNdArY\x0d\x0a"); + is $req->content->progress, 31, 'right progress'; + $req->parse("Content-Disposition: form-data; name=\"text1\"\x0d\x0a"); + $req->parse("\x0d\x0ahallo welt test123\n"); + $req->parse("\x0d\x0a------------0xKhTmLbOuNdArY\x0d\x0a"); + $req->parse("Content-Disposition: form-data; name=\"text2\"\x0d\x0a"); + $req->parse("\x0d\x0a\x0d\x0a------------0xKhTmLbOuNdArY\x0d\x0a"); + $req->parse('Content-Disposition: form-data;name="upload";file'); + $req->parse("name=\"hello.pl\"\x0d\x0a"); + $req->parse("Content-Type: application/octet-stream\x0d\x0a\x0d\x0a"); + $req->parse("#!/usr/bin/perl\n\n"); + $req->parse("use strict;\n"); + $req->parse("use warnings;\n\n"); + $req->parse("print \"Hello World :)\\n\"\n"); + $req->parse("\x0d\x0a------------0xKhTmLbOuNdArY--"); + is $req->content->progress, 416, 'right progress'; + ok $req->is_finished, 'request is finished'; + ok $req->content->is_multipart, 'multipart content'; + is $req->body, '', 'no content'; + is $req->method, 'GET', 'right method'; + is $req->version, '1.1', 'right version'; + is $req->url, '/foo/bar/baz.html?foo13', 'right URL'; + is $req->query_params, 'foo13', 'right parameters'; + is $req->headers->content_type, 'multipart/form-data; boundary=----------0xKhTmLbOuNdArY', + 'right "Content-Type" value'; + is $req->headers->content_length, 416, 'right "Content-Length" value'; + ok !$req->content->parts->[0]->is_multipart, 'no multipart content'; + ok !$req->content->parts->[1]->is_multipart, 'no multipart content'; + ok !$req->content->parts->[2]->is_multipart, 'no multipart content'; + ok !$req->content->parts->[0]->asset->is_file, 'stored in memory'; + is $req->content->parts->[0]->asset->slurp, "hallo welt test123\n", 'right content'; + is $req->body_params->to_hash->{text1}, "hallo welt test123\n", 'right value'; + is $req->body_params->to_hash->{text2}, '', 'right value'; + is $req->upload('upload')->filename, 'hello.pl', 'right filename'; + ok !$req->upload('upload')->asset->is_file, 'stored in memory'; + is $req->upload('upload')->asset->size, 69, 'right size'; + my $tempdir = tempdir; + my $file = $tempdir->child('MOJO_TMP.' . time . '.txt'); + is $req->upload('upload')->move_to($file)->filename, 'hello.pl', 'right filename'; + ok unlink($file), 'unlinked file'; + is $req->content->boundary, '----------0xKhTmLbOuNdArY', 'right boundary'; +}; + +subtest 'Parse HTTP 1.1 multipart request (too big for memory)' => sub { + my $req = Mojo::Message::Request->new; + $req->content->on( + body => sub { + my $single = shift; + $single->on( + upgrade => sub { + my ($single, $multi) = @_; + $multi->on( + part => sub { + my ($multi, $part) = @_; + $part->asset->max_memory_size(5); + } + ); + } + ); + } + ); + $req->parse("GET /foo/bar/baz.html?foo13 HTTP/1.1\x0d\x0a"); + $req->parse("Content-Length: 562\x0d\x0a"); + $req->parse('Content-Type: multipart/form-data; bo'); + $req->parse("undary=----------0xKhTmLbOuNdArY\x0d\x0a\x0d\x0a"); + $req->parse("\x0d\x0a------------0xKhTmLbOuNdArY\x0d\x0a"); + $req->parse("Content-Disposition: form-data; name=\"text1\"\x0d\x0a"); + $req->parse("\x0d\x0ahallo welt test123\n"); + $req->parse("\x0d\x0a------------0xKhTmLbOuNdArY\x0d\x0a"); + $req->parse("Content-Disposition: form-data; name=\"text2\"\x0d\x0a"); + $req->parse("\x0d\x0a\x0d\x0a------------0xKhTmLbOuNdArY\x0d\x0a"); + $req->parse('Content-Disposition: form-data; name="upload"; file'); + $req->parse("name=\"hello.pl\"\x0d\x0a"); + $req->parse("Content-Type: application/octet-stream\x0d\x0a\x0d\x0a"); + $req->parse("#!/usr/bin/perl\n\n"); + $req->parse("use strict;\n"); + $req->parse("use warnings;\n\n"); + $req->parse("print \"Hello World :)\\n\"\n"); + $req->parse("\x0d\x0a------------0xKhTmLbOuNdArY\x0d\x0a"); + $req->parse('Content-Disposition: form-data; name="upload"; filename'); + $req->parse("=\"bye.txt\"\x0d\x0a"); + $req->parse("Content-Type: application/octet-stream\x0d\x0a\x0d\x0a"); + $req->parse("Bye!\x0d\x0a------------0xKhTmLbOuNdArY--"); + ok $req->is_finished, 'request is finished'; + ok $req->content->is_multipart, 'multipart content'; + is $req->body, '', 'no content'; + is $req->method, 'GET', 'right method'; + is $req->version, '1.1', 'right version'; + is $req->url, '/foo/bar/baz.html?foo13', 'right URL'; + is $req->query_params, 'foo13', 'right parameters'; + is $req->headers->content_type, 'multipart/form-data; boundary=----------0xKhTmLbOuNdArY', + 'right "Content-Type" value'; + is $req->headers->content_length, 562, 'right "Content-Length" value'; + ok !$req->content->parts->[0]->is_multipart, 'no multipart content'; + ok !$req->content->parts->[1]->is_multipart, 'no multipart content'; + ok !$req->content->parts->[2]->is_multipart, 'no multipart content'; + ok $req->content->parts->[0]->asset->is_file, 'stored in file'; + is $req->content->parts->[0]->asset->slurp, "hallo welt test123\n", 'right content'; + is $req->body_params->to_hash->{text1}, "hallo welt test123\n", 'right value'; + is $req->body_params->to_hash->{text2}, '', 'right value'; + is $req->upload('upload')->filename, 'bye.txt', 'right filename'; + is $req->upload('upload')->asset->size, 4, 'right size'; + is $req->every_upload('upload')->[0]->filename, 'hello.pl', 'right filename'; + ok $req->every_upload('upload')->[0]->asset->is_file, 'stored in file'; + is $req->every_upload('upload')->[0]->asset->size, 69, 'right size'; + is $req->every_upload('upload')->[1]->filename, 'bye.txt', 'right filename'; + ok !$req->every_upload('upload')->[1]->asset->is_file, 'stored in memory'; + is $req->every_upload('upload')->[1]->asset->size, 4, 'right size'; +}; + +subtest 'Parse HTTP 1.1 multipart request (with callbacks and stream)' => sub { + my $req = Mojo::Message::Request->new; + my $stream = ''; + $req->content->on( + body => sub { + my $single = shift; + $single->on( + upgrade => sub { + my ($single, $multi) = @_; + $multi->on( + part => sub { + my ($multi, $part) = @_; + $part->on( + body => sub { + my $part = shift; + return unless $part->headers->content_disposition =~ /hello\.pl/; + $part->on( + read => sub { + my ($part, $chunk) = @_; + $stream .= $chunk; + } + ); + } + ); + } + ); + } + ); + } + ); + $req->parse("GET /foo/bar/baz.html?foo13 HTTP/1.1\x0d\x0a"); + $req->parse("Content-Length: 418\x0d\x0a"); + $req->parse('Content-Type: multipart/form-data; bo'); + $req->parse("undary=----------0xKhTmLbOuNdArY\x0d\x0a\x0d\x0a"); + $req->parse("\x0d\x0a------------0xKhTmLbOuNdArY\x0d\x0a"); + $req->parse("Content-Disposition: form-data; name=\"text1\"\x0d\x0a"); + $req->parse("\x0d\x0ahallo welt test123\n"); + $req->parse("\x0d\x0a------------0xKhTmLbOuNdArY\x0d\x0a"); + $req->parse("Content-Disposition: form-data; name=\"text2\"\x0d\x0a"); + $req->parse("\x0d\x0a\x0d\x0a------------0xKhTmLbOuNdArY\x0d\x0a"); + $req->parse('Content-Disposition: form-data; name="upload"; file'); + $req->parse("name=\"hello.pl\"\x0d\x0a"); + $req->parse("Content-Type: application/octet-stream\x0d\x0a\x0d\x0a"); + is $stream, '', 'no content'; + $req->parse("#!/usr/bin/perl\n\n"); + is $stream, '', 'no content'; + $req->parse("use strict;\n"); + is $stream, '', 'no content'; + $req->parse("use warnings;\n\n"); + is $stream, '#!/usr/bin/', 'right content'; + $req->parse("print \"Hello World :)\\n\"\n"); + is $stream, "#!/usr/bin/perl\n\nuse strict;\nuse war", 'right content'; + $req->parse("\x0d\x0a------------0xKhTmLbOuNdArY--"); + ok $req->is_finished, 'request is finished'; + ok $req->content->is_multipart, 'multipart content'; + is $req->method, 'GET', 'right method'; + is $req->version, '1.1', 'right version'; + is $req->url, '/foo/bar/baz.html?foo13', 'right URL'; + is $req->query_params, 'foo13', 'right parameters'; + is $req->headers->content_type, 'multipart/form-data; boundary=----------0xKhTmLbOuNdArY', + 'right "Content-Type" value'; + is $req->headers->content_length, 418, 'right "Content-Length" value'; + ok !$req->content->parts->[0]->is_multipart, 'no multipart content'; + ok !$req->content->parts->[1]->is_multipart, 'no multipart content'; + ok !$req->content->parts->[2]->is_multipart, 'no multipart content'; + is $req->content->parts->[0]->asset->slurp, "hallo welt test123\n", 'right content'; + is $req->body_params->to_hash->{text1}, "hallo welt test123\n", 'right value'; + is $req->body_params->to_hash->{text2}, '', 'right value'; + is $stream, "#!/usr/bin/perl\n\n" . "use strict;\n" . "use warnings;\n\n" . "print \"Hello World :)\\n\"\n", + 'right content'; +}; + +subtest 'Parse HTTP 1.1 multipart request (without upgrade)' => sub { + my $req = Mojo::Message::Request->new; + $req->content->auto_upgrade(0); + $req->parse("GET /foo/bar/baz.html?foo13 HTTP/1.1\x0d\x0a"); + $req->parse("Content-Length: 418\x0d\x0a"); + $req->parse('Content-Type: multipart/form-data; bo'); + $req->parse("undary=----------0xKhTmLbOuNdArY\x0d\x0a\x0d\x0a"); + $req->parse("\x0d\x0a------------0xKhTmLbOuNdArY\x0d\x0a"); + $req->parse("Content-Disposition: form-data; name=\"text1\"\x0d\x0a"); + $req->parse("\x0d\x0ahallo welt test123\n"); + $req->parse("\x0d\x0a------------0xKhTmLbOuNdArY\x0d\x0a"); + $req->parse("Content-Disposition: form-data; name=\"text2\"\x0d\x0a"); + $req->parse("\x0d\x0a\x0d\x0a------------0xKhTmLbOuNdArY\x0d\x0a"); + $req->parse('Content-Disposition: form-data; name="upload"; file'); + $req->parse("name=\"hello.pl\"\x0d\x0a"); + $req->parse("Content-Type: application/octet-stream\x0d\x0a\x0d\x0a"); + $req->parse("#!/usr/bin/perl\n\n"); + $req->parse("use strict;\n"); + $req->parse("use warnings;\n\n"); + $req->parse("print \"Hello World :)\\n\"\n"); + $req->parse("\x0d\x0a------------0xKhTmLbOuNdArY--"); + ok $req->is_finished, 'request is finished'; + ok !$req->content->is_multipart, 'no multipart content'; + is $req->method, 'GET', 'right method'; + is $req->version, '1.1', 'right version'; + is $req->url, '/foo/bar/baz.html?foo13', 'right URL'; + is $req->query_params, 'foo13', 'right parameters'; + is $req->headers->content_type, 'multipart/form-data; boundary=----------0xKhTmLbOuNdArY', + 'right "Content-Type" value'; + is $req->headers->content_length, 418, 'right "Content-Length" value'; + ok !$req->content->is_multipart, 'no multipart content'; + like $req->content->asset->slurp, qr/------------0xKhTmLbOuNdArY--$/, 'right content'; +}; + +subtest 'Parse HTTP 1.1 multipart request with "0" filename' => sub { + my $req = Mojo::Message::Request->new; + $req->parse("GET /foo/bar/baz.html?foo13 HTTP/1.1\x0d\x0a"); + $req->parse("Content-Length: 410\x0d\x0a"); + $req->parse('Content-Type: multipart/form-data; bo'); + $req->parse("undary=----------0xKhTmLbOuNdArY\x0d\x0a\x0d\x0a"); + $req->parse("\x0d\x0a------------0xKhTmLbOuNdArY\x0d\x0a"); + $req->parse("Content-Disposition: form-data; name=\"text1\"\x0d\x0a"); + $req->parse("\x0d\x0ahallo welt test123\n"); + $req->parse("\x0d\x0a------------0xKhTmLbOuNdArY\x0d\x0a"); + $req->parse("Content-Disposition: form-data; name=\"text2\"\x0d\x0a"); + $req->parse("\x0d\x0a\x0d\x0a------------0xKhTmLbOuNdArY\x0d\x0a"); + $req->parse('Content-Disposition: form-data; name="upload"; file'); + $req->parse("name=\"0\"\x0d\x0a"); + $req->parse("Content-Type: application/octet-stream\x0d\x0a\x0d\x0a"); + $req->parse("#!/usr/bin/perl\n\n"); + $req->parse("use strict;\n"); + $req->parse("use warnings;\n\n"); + $req->parse("print \"Hello World :)\\n\"\n"); + $req->parse("\x0d\x0a------------0xKhTmLbOuNdArY--"); + ok $req->is_finished, 'request is finished'; + ok $req->content->is_multipart, 'no multipart content'; + is $req->method, 'GET', 'right method'; + is $req->version, '1.1', 'right version'; + is $req->url, '/foo/bar/baz.html?foo13', 'right URL'; + is $req->query_params, 'foo13', 'right parameters'; + is $req->headers->content_type, 'multipart/form-data; boundary=----------0xKhTmLbOuNdArY', + 'right "Content-Type" value'; + is $req->headers->content_length, 410, 'right "Content-Length" value'; + ok !$req->content->parts->[0]->is_multipart, 'no multipart content'; + ok !$req->content->parts->[1]->is_multipart, 'no multipart content'; + ok !$req->content->parts->[2]->is_multipart, 'no multipart content'; + is $req->content->parts->[0]->asset->slurp, "hallo welt test123\n", 'right content'; + is $req->body_params->to_hash->{text1}, "hallo welt test123\n", 'right value'; + is $req->body_params->to_hash->{text2}, '', 'right value'; + is $req->body_params->to_hash->{upload}, undef, 'not a body parameter'; + is $req->upload('upload')->filename, '0', 'right filename'; + ok !$req->upload('upload')->asset->is_file, 'stored in memory'; + is $req->upload('upload')->asset->size, 69, 'right size'; +}; + +subtest 'Parse full HTTP 1.1 proxy request with basic authentication' => sub { + my $req = Mojo::Message::Request->new; + $req->parse("GET http://127.0.0.1/foo/bar#baz HTTP/1.1\x0d\x0a"); + $req->parse("Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==\x0d\x0a"); + $req->parse("Host: 127.0.0.1\x0d\x0a"); + $req->parse("Proxy-Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==\x0d\x0a"); + $req->parse("Content-Length: 13\x0d\x0a\x0d\x0a"); + $req->parse("Hello World!\n"); + ok $req->is_finished, 'request is finished'; + is $req->method, 'GET', 'right method'; + is $req->version, '1.1', 'right version'; + is $req->url->base, 'http://127.0.0.1', 'right base URL'; + is $req->url->base->userinfo, 'Aladdin:open sesame', 'right base userinfo'; + is $req->url, 'http://127.0.0.1/foo/bar', 'right URL'; + is $req->proxy->userinfo, 'Aladdin:open sesame', 'right proxy userinfo'; +}; + +subtest 'Parse full HTTP 1.1 proxy connect request with basic authentication' => sub { + my $req = Mojo::Message::Request->new; + $req->parse("CONNECT 127.0.0.1:3000 HTTP/1.1\x0d\x0a"); + $req->parse("Host: 127.0.0.1:3000\x0d\x0a"); + $req->parse("Proxy-Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==\x0d\x0a"); + $req->parse("Content-Length: 0\x0d\x0a\x0d\x0a"); + ok $req->is_finished, 'request is finished'; + is $req->method, 'CONNECT', 'right method'; + is $req->version, '1.1', 'right version'; + is $req->url, '//127.0.0.1:3000', 'right URL'; + is $req->url->host, '127.0.0.1', 'right host'; + is $req->url->port, '3000', 'right port'; + is $req->proxy->userinfo, 'Aladdin:open sesame', 'right proxy userinfo'; +}; + +subtest 'Build minimal HTTP 1.1 request' => sub { + my $req = Mojo::Message::Request->new; + $req->method('GET'); + $req->url->parse('http://127.0.0.1/'); + $req = Mojo::Message::Request->new->parse($req->to_string); + ok $req->is_finished, 'request is finished'; + is $req->method, 'GET', 'right method'; + is $req->version, '1.1', 'right version'; + is $req->url, '/', 'right URL'; + is $req->url->to_abs, 'http://127.0.0.1/', 'right absolute URL'; + is $req->headers->host, '127.0.0.1', 'right "Host" value'; +}; + +subtest 'Build HTTP 1.1 start-line and header' => sub { + my $req = Mojo::Message::Request->new; + $req->method('GET'); + $req->url->parse('http://127.0.0.1/foo/bar'); + $req->headers->expect('100-continue'); + $req = Mojo::Message::Request->new->parse($req->to_string); + ok $req->is_finished, 'request is finished'; + is $req->method, 'GET', 'right method'; + is $req->version, '1.1', 'right version'; + is $req->url, '/foo/bar', 'right URL'; + is $req->url->to_abs, 'http://127.0.0.1/foo/bar', 'right absolute URL'; + is $req->headers->expect, '100-continue', 'right "Expect" value'; + is $req->headers->host, '127.0.0.1', 'right "Host" value'; +}; + +subtest 'Build HTTP 1.1 start-line and header (with clone)' => sub { + my $req = Mojo::Message::Request->new; + $req->method('GET'); + $req->url->parse('http://127.0.0.1/foo/bar'); + $req->headers->expect('100-continue'); + my $clone = $req->clone; + $req = Mojo::Message::Request->new->parse($req->to_string); + ok $req->is_finished, 'request is finished'; + is $req->method, 'GET', 'right method'; + is $req->version, '1.1', 'right version'; + is $req->url, '/foo/bar', 'right URL'; + is $req->url->to_abs, 'http://127.0.0.1/foo/bar', 'right absolute URL'; + is $req->headers->expect, '100-continue', 'right "Expect" value'; + is $req->headers->host, '127.0.0.1', 'right "Host" value'; + $clone = Mojo::Message::Request->new->parse($clone->to_string); + ok $clone->is_finished, 'request is finished'; + is $clone->method, 'GET', 'right method'; + is $clone->version, '1.1', 'right version'; + is $clone->url, '/foo/bar', 'right URL'; + is $clone->url->to_abs, 'http://127.0.0.1/foo/bar', 'right absolute URL'; + is $clone->headers->expect, '100-continue', 'right "Expect" value'; + is $clone->headers->host, '127.0.0.1', 'right "Host" value'; +}; + +subtest 'Build HTTP 1.1 start-line and header (with clone and changes)' => sub { + my $req = Mojo::Message::Request->new; + $req->method('GET'); + $req->url->parse('http://127.0.0.1/foo/bar'); + $req->headers->expect('100-continue'); + my $clone = $req->clone; + $clone->method('POST'); + $clone->headers->expect('nothing'); + $clone->version('1.2'); + push @{$clone->url->path->parts}, 'baz'; + $req = Mojo::Message::Request->new->parse($req->to_string); + ok $req->is_finished, 'request is finished'; + is $req->method, 'GET', 'right method'; + is $req->version, '1.1', 'right version'; + is $req->url, '/foo/bar', 'right URL'; + is $req->url->to_abs, 'http://127.0.0.1/foo/bar', 'right absolute URL'; + is $req->headers->expect, '100-continue', 'right "Expect" value'; + is $req->headers->host, '127.0.0.1', 'right "Host" value'; + $clone = Mojo::Message::Request->new->parse($clone->to_string); + ok $clone->is_finished, 'request is finished'; + is $clone->method, 'POST', 'right method'; + is $clone->version, '1.2', 'right version'; + is $clone->url, '/foo/bar/baz', 'right URL'; + is $clone->url->to_abs, 'http://127.0.0.1/foo/bar/baz', 'right absolute URL'; + is $clone->headers->expect, 'nothing', 'right "Expect" value'; + is $clone->headers->host, '127.0.0.1', 'right "Host" value'; +}; + +subtest 'Build full HTTP 1.1 request' => sub { + my $req = Mojo::Message::Request->new; + my $finished = undef; + $req->on(finish => sub { $finished = shift->is_finished }); + $req->method('get'); + $req->url->parse('http://127.0.0.1/foo/bar'); + $req->headers->expect('100-continue'); + $req->body("Hello World!\n"); + $req = Mojo::Message::Request->new->parse($req->to_string); + ok $req->is_finished, 'request is finished'; + is $req->method, 'GET', 'right method'; + is $req->version, '1.1', 'right version'; + is $req->url, '/foo/bar', 'right URL'; + is $req->url->to_abs, 'http://127.0.0.1/foo/bar', 'right absolute URL'; + is $req->headers->expect, '100-continue', 'right "Expect" value'; + is $req->headers->host, '127.0.0.1', 'right "Host" value'; + is $req->headers->content_length, '13', 'right "Content-Length" value'; + is $req->body, "Hello World!\n", 'right content'; + ok $finished, 'finish event has been emitted'; + ok $req->is_finished, 'request is finished'; +}; + +subtest 'Build HTTP 1.1 request parts with progress' => sub { + my $req = Mojo::Message::Request->new; + my $state; + my $finished = undef; + my $progress = 0; + $req->on(finish => sub { $finished = shift->is_finished }); + $req->on( + progress => sub { + my ($req, $part, $offset) = @_; + $state = $part; + $progress += $offset; + } + ); + $req->method('get'); + $req->url->parse('http://127.0.0.1/foo/bar'); + $req->headers->expect('100-continue'); + $req->body("Hello World!\n"); + ok !$state, 'no state'; + ok !$progress, 'no progress'; + ok !$finished, 'not finished'; + ok $req->build_start_line, 'built start-line'; + is $state, 'start_line', 'made progress on start_line'; + ok $progress, 'made progress'; + $progress = 0; + ok !$finished, 'not finished'; + ok $req->build_headers, 'built headers'; + is $state, 'headers', 'made progress on headers'; + ok $progress, 'made progress'; + $progress = 0; + ok !$finished, 'not finished'; + ok $req->build_body, 'built body'; + is $state, 'body', 'made progress on headers'; + ok $progress, 'made progress'; + ok $finished, 'finished'; +}; + +subtest 'Build full HTTP 1.1 request (with clone)' => sub { + my $req = Mojo::Message::Request->new; + my $finished = undef; + $req->on(finish => sub { $finished = shift->is_finished }); + $req->method('get'); + $req->url->parse('http://127.0.0.1/foo/bar'); + $req->headers->expect('100-continue'); + $req->body("Hello World!\n"); + my $clone = $req->clone; + $req = Mojo::Message::Request->new->parse($req->to_string); + ok $req->is_finished, 'request is finished'; + is $req->method, 'GET', 'right method'; + is $req->version, '1.1', 'right version'; + is $req->url, '/foo/bar', 'right URL'; + is $req->url->to_abs, 'http://127.0.0.1/foo/bar', 'right absolute URL'; + is $req->headers->expect, '100-continue', 'right "Expect" value'; + is $req->headers->host, '127.0.0.1', 'right "Host" value'; + is $req->headers->content_length, '13', 'right "Content-Length" value'; + is $req->body, "Hello World!\n", 'right content'; + ok $finished, 'finish event has been emitted'; + ok $req->is_finished, 'request is finished'; + $finished = undef; + $clone = Mojo::Message::Request->new->parse($clone->to_string); + ok $clone->is_finished, 'request is finished'; + is $clone->method, 'GET', 'right method'; + is $clone->version, '1.1', 'right version'; + is $clone->url, '/foo/bar', 'right URL'; + is $clone->url->to_abs, 'http://127.0.0.1/foo/bar', 'right absolute URL'; + is $clone->headers->expect, '100-continue', 'right "Expect" value'; + is $clone->headers->host, '127.0.0.1', 'right "Host" value'; + is $clone->headers->content_length, '13', 'right "Content-Length" value'; + is $clone->body, "Hello World!\n", 'right content'; + ok !$finished, 'finish event has been emitted'; + ok $clone->is_finished, 'request is finished'; +}; + +subtest 'Build full HTTP 1.1 request (roundtrip)' => sub { + my $req = Mojo::Message::Request->new; + $req->method('GET'); + $req->url->parse('http://127.0.0.1/foo/bar'); + $req->headers->expect('100-continue'); + $req->body("Hello World!\n"); + $req = Mojo::Message::Request->new->parse($req->to_string); + ok $req->is_finished, 'request is finished'; + is $req->method, 'GET', 'right method'; + is $req->version, '1.1', 'right version'; + is $req->url, '/foo/bar', 'right URL'; + is $req->url->to_abs, 'http://127.0.0.1/foo/bar', 'right absolute URL'; + is $req->headers->expect, '100-continue', 'right "Expect" value'; + is $req->headers->host, '127.0.0.1', 'right "Host" value'; + is $req->headers->content_length, '13', 'right "Content-Length" value'; + is $req->body, "Hello World!\n", 'right content'; + my $req2 = Mojo::Message::Request->new->parse($req->to_string); + is $req->content->leftovers, '', 'no leftovers'; + is $req->error, undef, 'no error'; + is $req2->content->leftovers, '', 'no leftovers'; + is $req2->error, undef, 'no error'; + ok $req2->is_finished, 'request is finished'; + is $req2->method, 'GET', 'right method'; + is $req2->version, '1.1', 'right version'; + is $req2->url, '/foo/bar', 'right URL'; + is $req2->url->to_abs, 'http://127.0.0.1/foo/bar', 'right absolute URL'; + is $req2->headers->expect, '100-continue', 'right "Expect" value'; + is $req2->headers->host, '127.0.0.1', 'right "Host" value'; + is $req->headers->content_length, '13', 'right "Content-Length" value'; + is $req->body, "Hello World!\n", 'right content'; +}; + +subtest 'Build HTTP 1.1 request body' => sub { + my $req = Mojo::Message::Request->new; + my $finished = undef; + $req->on(finish => sub { $finished = shift->is_finished }); + $req->method('get'); + $req->url->parse('http://127.0.0.1/foo/bar'); + $req->headers->expect('100-continue'); + $req->body("Hello World!\n"); + my $i = 0; + while (my $chunk = $req->get_body_chunk($i)) { $i += length $chunk } + ok $finished, 'finish event has been emitted'; + ok $req->is_finished, 'request is finished'; +}; + +subtest 'Build WebSocket handshake request' => sub { + my $req = Mojo::Message::Request->new; + my $finished = undef; + $req->on(finish => sub { $finished = shift->is_finished }); + $req->method('GET'); + $req->url->parse('http://example.com/demo'); + $req->headers->host('example.com'); + $req->headers->connection('Upgrade'); + $req->headers->sec_websocket_accept('abcdef='); + $req->headers->sec_websocket_protocol('sample'); + $req->headers->upgrade('websocket'); + $req = Mojo::Message::Request->new->parse($req->to_string); + ok $req->is_finished, 'request is finished'; + is $req->method, 'GET', 'right method'; + is $req->version, '1.1', 'right version'; + is $req->url, '/demo', 'right URL'; + is $req->url->to_abs, 'http://example.com/demo', 'right absolute URL'; + is $req->headers->connection, 'Upgrade', 'right "Connection" value'; + is $req->headers->upgrade, 'websocket', 'right "Upgrade" value'; + is $req->headers->host, 'example.com', 'right "Host" value'; + is $req->headers->content_length, 0, 'right "Content-Length" value'; + is $req->headers->sec_websocket_accept, 'abcdef=', 'right "Sec-WebSocket-Key" value'; + is $req->headers->sec_websocket_protocol, 'sample', 'right "Sec-WebSocket-Protocol" value'; + is $req->body, '', 'no content'; + ok $finished, 'finish event has been emitted'; + ok $req->is_finished, 'request is finished'; +}; + +subtest 'Build WebSocket handshake request (with clone)' => sub { + my $req = Mojo::Message::Request->new; + $req->method('GET'); + $req->url->parse('http://example.com/demo'); + $req->headers->host('example.com'); + $req->headers->connection('Upgrade'); + $req->headers->sec_websocket_accept('abcdef='); + $req->headers->sec_websocket_protocol('sample'); + $req->headers->upgrade('websocket'); + my $clone = $req->clone; + $req = Mojo::Message::Request->new->parse($req->to_string); + ok $req->is_finished, 'request is finished'; + is $req->method, 'GET', 'right method'; + is $req->version, '1.1', 'right version'; + is $req->url, '/demo', 'right URL'; + is $req->url->to_abs, 'http://example.com/demo', 'right absolute URL'; + is $req->headers->connection, 'Upgrade', 'right "Connection" value'; + is $req->headers->upgrade, 'websocket', 'right "Upgrade" value'; + is $req->headers->host, 'example.com', 'right "Host" value'; + is $req->headers->content_length, 0, 'right "Content-Length" value'; + is $req->headers->sec_websocket_accept, 'abcdef=', 'right "Sec-WebSocket-Key" value'; + is $req->headers->sec_websocket_protocol, 'sample', 'right "Sec-WebSocket-Protocol" value'; + is $req->body, '', 'no content'; + ok $req->is_finished, 'request is finished'; + $clone = Mojo::Message::Request->new->parse($clone->to_string); + ok $clone->is_finished, 'request is finished'; + is $clone->method, 'GET', 'right method'; + is $clone->version, '1.1', 'right version'; + is $clone->url, '/demo', 'right URL'; + is $clone->url->to_abs, 'http://example.com/demo', 'right absolute URL'; + is $clone->headers->connection, 'Upgrade', 'right "Connection" value'; + is $clone->headers->upgrade, 'websocket', 'right "Upgrade" value'; + is $clone->headers->host, 'example.com', 'right "Host" value'; + is $req->headers->content_length, 0, 'right "Content-Length" value'; + is $clone->headers->sec_websocket_accept, 'abcdef=', 'right "Sec-WebSocket-Key" value'; + is $clone->headers->sec_websocket_protocol, 'sample', 'right "Sec-WebSocket-Protocol" value'; + is $clone->body, '', 'no content'; + ok $clone->is_finished, 'request is finished'; +}; + +subtest 'Build WebSocket handshake proxy request' => sub { + my $req = Mojo::Message::Request->new; + my $finished = undef; + $req->on(finish => sub { $finished = shift->is_finished }); + $req->method('GET'); + $req->url->parse('http://example.com/demo'); + $req->headers->host('example.com'); + $req->headers->connection('Upgrade'); + $req->headers->sec_websocket_accept('abcdef='); + $req->headers->sec_websocket_protocol('sample'); + $req->headers->upgrade('websocket'); + $req->proxy(Mojo::URL->new('http://127.0.0.2:8080')); + $req = Mojo::Message::Request->new->parse($req->to_string); + ok $req->is_finished, 'request is finished'; + is $req->method, 'GET', 'right method'; + is $req->version, '1.1', 'right version'; + is $req->url, '/demo', 'right URL'; + is $req->url->to_abs, 'http://example.com/demo', 'right absolute URL'; + is $req->headers->connection, 'Upgrade', 'right "Connection" value'; + is $req->headers->upgrade, 'websocket', 'right "Upgrade" value'; + is $req->headers->host, 'example.com', 'right "Host" value'; + is $req->headers->content_length, 0, 'right "Content-Length" value'; + is $req->headers->sec_websocket_accept, 'abcdef=', 'right "Sec-WebSocket-Key" value'; + is $req->headers->sec_websocket_protocol, 'sample', 'right "Sec-WebSocket-Protocol" value'; + is $req->body, '', 'no content'; + ok $finished, 'finish event has been emitted'; + ok $req->is_finished, 'request is finished'; +}; + +subtest 'Build full HTTP 1.1 proxy request' => sub { + my $req = Mojo::Message::Request->new; + $req->method('GET'); + $req->url->parse('http://127.0.0.1/foo/bar'); + $req->headers->expect('100-continue'); + $req->body("Hello World!\n"); + $req->proxy(Mojo::URL->new('http://127.0.0.2:8080')); + $req = Mojo::Message::Request->new->parse($req->to_string); + ok $req->is_finished, 'request is finished'; + is $req->method, 'GET', 'right method'; + is $req->version, '1.1', 'right version'; + is $req->url, 'http://127.0.0.1/foo/bar', 'right URL'; + is $req->url->to_abs, 'http://127.0.0.1/foo/bar', 'right absolute URL'; + is $req->headers->expect, '100-continue', 'right "Expect" value'; + is $req->headers->host, '127.0.0.1', 'right "Host" value'; + is $req->headers->content_length, '13', 'right "Content-Length" value'; + is $req->body, "Hello World!\n", 'right content'; +}; + +subtest 'Build full HTTP 1.1 proxy request (proxy disabled)' => sub { + my $req = Mojo::Message::Request->new; + $req->method('GET'); + $req->url->parse('http://127.0.0.1/foo/bar'); + $req->body("Hello World!\n"); + $req->via_proxy(0)->proxy(Mojo::URL->new('http://127.0.0.2:8080')); + $req = Mojo::Message::Request->new->parse($req->to_string); + ok $req->is_finished, 'request is finished'; + is $req->method, 'GET', 'right method'; + is $req->version, '1.1', 'right version'; + is $req->url, '/foo/bar', 'right URL'; + is $req->url->to_abs, 'http://127.0.0.1/foo/bar', 'right absolute URL'; + is $req->headers->host, '127.0.0.1', 'right "Host" value'; + is $req->headers->content_length, '13', 'right "Content-Length" value'; + is $req->body, "Hello World!\n", 'right content'; +}; + +subtest 'Build full HTTP 1.1 proxy request (HTTPS)' => sub { + my $req = Mojo::Message::Request->new; + $req->method('GET'); + $req->url->parse('https://127.0.0.1/foo/bar'); + $req->headers->expect('100-continue'); + $req->body("Hello World!\n"); + $req->proxy(Mojo::URL->new('http://127.0.0.2:8080')); + $req = Mojo::Message::Request->new->parse($req->to_string); + ok $req->is_finished, 'request is finished'; + is $req->method, 'GET', 'right method'; + is $req->version, '1.1', 'right version'; + is $req->url, '/foo/bar', 'right URL'; + is $req->url->to_abs, 'http://127.0.0.1/foo/bar', 'right absolute URL'; + is $req->headers->expect, '100-continue', 'right "Expect" value'; + is $req->headers->host, '127.0.0.1', 'right "Host" value'; + is $req->headers->content_length, '13', 'right "Content-Length" value'; + is $req->body, "Hello World!\n", 'right content'; +}; + +subtest 'Build full HTTP 1.1 proxy request with basic authentication' => sub { + my $req = Mojo::Message::Request->new; + $req->method('GET'); + $req->url->parse('http://Aladdin:open%20sesame@127.0.0.1/foo/bar'); + $req->headers->expect('100-continue'); + $req->body("Hello World!\n"); + $req->proxy(Mojo::URL->new('http://Aladdin:open%20sesame@127.0.0.2:8080')); + $req = Mojo::Message::Request->new->parse($req->to_string); + ok $req->is_finished, 'request is finished'; + is $req->method, 'GET', 'right method'; + is $req->version, '1.1', 'right version'; + is $req->url, 'http://127.0.0.1/foo/bar', 'right URL'; + is $req->url->to_abs, 'http://127.0.0.1/foo/bar', 'right absolute URL'; + is $req->proxy->userinfo, 'Aladdin:open sesame', 'right proxy userinfo'; + is $req->headers->authorization, 'Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==', 'right "Authorization" value'; + is $req->headers->expect, '100-continue', 'right "Expect" value'; + is $req->headers->host, '127.0.0.1', 'right "Host" value'; + is $req->headers->proxy_authorization, 'Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==', 'right "Proxy-Authorization" value'; + is $req->headers->content_length, '13', 'right "Content-Length" value'; + is $req->body, "Hello World!\n", 'right content'; +}; + +subtest 'Build full HTTP 1.1 proxy request with basic authentication (and clone)' => sub { + my $req = Mojo::Message::Request->new; + $req->method('GET'); + $req->url->parse('http://Aladdin:open%20sesame@127.0.0.1/foo/bar'); + $req->headers->expect('100-continue'); + $req->body("Hello World!\n"); + $req->proxy(Mojo::URL->new('http://Aladdin:open%20sesame@127.0.0.2:8080')); + my $clone = $req->clone; + $req = Mojo::Message::Request->new->parse($req->to_string); + ok $req->is_finished, 'request is finished'; + is $req->method, 'GET', 'right method'; + is $req->version, '1.1', 'right version'; + is $req->url, 'http://127.0.0.1/foo/bar', 'right URL'; + is $req->url->to_abs, 'http://127.0.0.1/foo/bar', 'right absolute URL'; + is $req->proxy->userinfo, 'Aladdin:open sesame', 'right proxy userinfo'; + is $req->headers->authorization, 'Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==', 'right "Authorization" value'; + is $req->headers->expect, '100-continue', 'right "Expect" value'; + is $req->headers->host, '127.0.0.1', 'right "Host" value'; + is $req->headers->proxy_authorization, 'Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==', 'right "Proxy-Authorization" value'; + is $req->headers->content_length, '13', 'right "Content-Length" value'; + is $req->body, "Hello World!\n", 'right content'; + $clone = Mojo::Message::Request->new->parse($clone->to_string); + ok $clone->is_finished, 'request is finished'; + is $clone->method, 'GET', 'right method'; + is $clone->version, '1.1', 'right version'; + is $clone->url, 'http://127.0.0.1/foo/bar', 'right URL'; + is $clone->url->to_abs, 'http://127.0.0.1/foo/bar', 'right absolute URL'; + is $clone->proxy->userinfo, 'Aladdin:open sesame', 'right proxy userinfo'; + is $clone->headers->authorization, 'Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==', 'right "Authorization" value'; + is $clone->headers->expect, '100-continue', 'right "Expect" value'; + is $clone->headers->host, '127.0.0.1', 'right "Host" value'; + is $clone->headers->proxy_authorization, 'Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==', 'right "Proxy-Authorization" value'; + is $clone->headers->content_length, '13', 'right "Content-Length" value'; + is $clone->body, "Hello World!\n", 'right content'; +}; + +subtest 'Build full HTTP 1.1 proxy connect request with basic authentication' => sub { + my $req = Mojo::Message::Request->new; + $req->method('CONNECT'); + $req->url->parse('http://Aladdin:open%20sesame@bücher.ch:3000/foo/bar'); + $req->proxy(Mojo::URL->new('http://Aladdin:open%20sesame@127.0.0.2:8080')); + $req = Mojo::Message::Request->new->parse($req->to_string); + ok $req->is_finished, 'request is finished'; + is $req->method, 'CONNECT', 'right method'; + is $req->version, '1.1', 'right version'; + is $req->url, '//xn--bcher-kva.ch:3000', 'right URL'; + is $req->url->host, 'xn--bcher-kva.ch', 'right host'; + is $req->url->port, '3000', 'right port'; + is $req->url->to_abs, 'http://xn--bcher-kva.ch:3000', 'right absolute URL'; + is $req->proxy->userinfo, 'Aladdin:open sesame', 'right proxy userinfo'; + is $req->headers->authorization, 'Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==', 'right "Authorization" value'; + is $req->headers->host, 'xn--bcher-kva.ch:3000', 'right "Host" value'; + is $req->headers->proxy_authorization, 'Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==', 'right "Proxy-Authorization" value'; +}; + +subtest 'Build HTTP 1.1 multipart request' => sub { + my $req = Mojo::Message::Request->new; + $req->method('GET'); + $req->url->parse('http://127.0.0.1/foo/bar'); + $req->content(Mojo::Content::MultiPart->new); + $req->headers->content_type('multipart/mixed; boundary=7am1X'); + push @{$req->content->parts}, Mojo::Content::Single->new; + $req->content->parts->[-1]->asset->add_chunk('Hallo Welt lalalala!'); + my $content = Mojo::Content::Single->new; + $content->asset->add_chunk("lala\nfoobar\nperl rocks\n"); + $content->headers->content_type('text/plain'); + push @{$req->content->parts}, $content; + $req = Mojo::Message::Request->new->parse($req->to_string); + ok $req->is_finished, 'request is finished'; + is $req->method, 'GET', 'right method'; + is $req->version, '1.1', 'right version'; + is $req->url, '/foo/bar', 'right URL'; + is $req->url->to_abs, 'http://127.0.0.1/foo/bar', 'right absolute URL'; + is $req->headers->host, '127.0.0.1', 'right "Host" value'; + is $req->headers->content_length, '106', 'right "Content-Length" value'; + is $req->headers->content_type, 'multipart/mixed; boundary=7am1X', 'right "Content-Type" value'; + is $req->content->parts->[0]->asset->slurp, 'Hallo Welt lalalala!', 'right content'; + is $req->content->parts->[1]->headers->content_type, 'text/plain', 'right "Content-Type" value'; + is $req->content->parts->[1]->asset->slurp, "lala\nfoobar\nperl rocks\n", 'right content'; +}; + +subtest 'Build HTTP 1.1 multipart request (with clone)' => sub { + my $req = Mojo::Message::Request->new; + $req->method('GET'); + $req->url->parse('http://127.0.0.1/foo/bar'); + $req->content(Mojo::Content::MultiPart->new); + $req->headers->content_type('multipart/mixed; boundary=7am1X'); + push @{$req->content->parts}, Mojo::Content::Single->new; + $req->content->parts->[-1]->asset->add_chunk('Hallo Welt lalalala!'); + my $content = Mojo::Content::Single->new; + $content->asset->add_chunk("lala\nfoobar\nperl rocks\n"); + $content->headers->content_type('text/plain'); + push @{$req->content->parts}, $content; + my $clone = $req->clone; + $req = Mojo::Message::Request->new->parse($req->to_string); + ok $req->is_finished, 'request is finished'; + is $req->method, 'GET', 'right method'; + is $req->version, '1.1', 'right version'; + is $req->url, '/foo/bar', 'right URL'; + is $req->url->to_abs, 'http://127.0.0.1/foo/bar', 'right absolute URL'; + is $req->headers->host, '127.0.0.1', 'right "Host" value'; + is $req->headers->content_length, '106', 'right "Content-Length" value'; + is $req->headers->content_type, 'multipart/mixed; boundary=7am1X', 'right "Content-Type" value'; + is $req->content->parts->[0]->asset->slurp, 'Hallo Welt lalalala!', 'right content'; + is $req->content->parts->[1]->headers->content_type, 'text/plain', 'right "Content-Type" value'; + is $req->content->parts->[1]->asset->slurp, "lala\nfoobar\nperl rocks\n", 'right content'; + $clone = Mojo::Message::Request->new->parse($clone->to_string); + ok $clone->is_finished, 'request is finished'; + is $clone->method, 'GET', 'right method'; + is $clone->version, '1.1', 'right version'; + is $clone->url, '/foo/bar', 'right URL'; + is $clone->url->to_abs, 'http://127.0.0.1/foo/bar', 'right absolute URL'; + is $clone->headers->host, '127.0.0.1', 'right "Host" value'; + is $clone->headers->content_length, '106', 'right "Content-Length" value'; + is $clone->headers->content_type, 'multipart/mixed; boundary=7am1X', 'right "Content-Type" value'; + is $clone->content->parts->[0]->asset->slurp, 'Hallo Welt lalalala!', 'right content'; + is $clone->content->parts->[1]->headers->content_type, 'text/plain', 'right "Content-Type" value'; + is $clone->content->parts->[1]->asset->slurp, "lala\nfoobar\nperl rocks\n", 'right content'; +}; + +subtest 'Build HTTP 1.1 chunked request' => sub { + my $req = Mojo::Message::Request->new; + $req->method('GET'); + $req->url->parse('http://127.0.0.1:8080/foo/bar'); + $req->headers->transfer_encoding('chunked'); + my $counter; + $req->on(progress => sub { $counter++ }); + $req->content->write_chunk( + 'hello world!' => sub { + shift->write_chunk( + "hello world2!\n\n" => sub { + my $self = shift; + $self->write_chunk(''); + } + ); + } + ); + is $req->clone, undef, 'dynamic requests cannot be cloned'; + $req = Mojo::Message::Request->new->parse($req->to_string); + ok $req->is_finished, 'request is finished'; + is $req->method, 'GET', 'right method'; + is $req->version, '1.1', 'right version'; + is $req->url, '/foo/bar', 'right URL'; + is $req->url->to_abs, 'http://127.0.0.1:8080/foo/bar', 'right absolute URL'; + is $req->headers->host, '127.0.0.1:8080', 'right "Host" value'; + is $req->headers->transfer_encoding, undef, 'no "Transfer-Encoding" value'; + is $req->body, "hello world!hello world2!\n\n", 'right content'; + ok $counter, 'right counter'; +}; + +subtest 'Build HTTP 1.1 chunked request' => sub { + my $req = Mojo::Message::Request->new; + $req->method('GET'); + $req->url->parse('http://127.0.0.1'); + $req->content->write_chunk('hello world!'); + $req->content->write_chunk("hello world2!\n\n"); + $req->content->write_chunk(''); + is $req->clone, undef, 'dynamic requests cannot be cloned'; + $req = Mojo::Message::Request->new->parse($req->to_string); + ok $req->is_finished, 'request is finished'; + is $req->method, 'GET', 'right method'; + is $req->version, '1.1', 'right version'; + is $req->url, '/', 'right URL'; + is $req->url->to_abs, 'http://127.0.0.1/', 'right absolute URL'; + is $req->headers->host, '127.0.0.1', 'right "Host" value'; + is $req->headers->transfer_encoding, undef, 'no "Transfer-Encoding" value'; + is $req->body, "hello world!hello world2!\n\n", 'right content'; +}; + +subtest 'Build full HTTP 1.1 request with cookies' => sub { + my $req = Mojo::Message::Request->new; + $req->method('GET'); + $req->url->parse('http://127.0.0.1/foo/bar?0'); + $req->headers->expect('100-continue'); + $req->cookies({name => 'foo', value => 'bar'}, {name => 'bar', value => 'baz'}); + $req->cookies(Mojo::Cookie::Request->new(name => 'baz', value => 'yada')); + $req->body("Hello World!\n"); + ok !!$req->to_string, 'message built'; + my $req2 = Mojo::Message::Request->new; + $req2->parse($req->to_string); + ok $req2->is_finished, 'request is finished'; + is $req2->method, 'GET', 'right method'; + is $req2->version, '1.1', 'right version'; + is $req2->headers->expect, '100-continue', 'right "Expect" value'; + is $req2->headers->host, '127.0.0.1', 'right "Host" value'; + is $req2->headers->content_length, 13, 'right "Content-Length" value'; + is $req2->headers->cookie, 'foo=bar; bar=baz; baz=yada', 'right "Cookie" value'; + is $req2->url, '/foo/bar?0', 'right URL'; + is $req2->url->to_abs, 'http://127.0.0.1/foo/bar?0', 'right absolute URL'; + ok defined $req2->cookie('foo'), 'cookie "foo" exists'; + ok defined $req2->cookie('bar'), 'cookie "bar" exists'; + ok defined $req2->cookie('baz'), 'cookie "baz" exists'; + ok !defined $req2->cookie('yada'), 'cookie "yada" does not exist'; + is $req2->cookie('foo')->value, 'bar', 'right value'; + is $req2->cookie('bar')->value, 'baz', 'right value'; + is $req2->cookie('baz')->value, 'yada', 'right value'; + is $req2->body, "Hello World!\n", 'right content'; +}; + +subtest 'Build HTTP 1.1 request with cookies sharing the same name' => sub { + my $req = Mojo::Message::Request->new; + $req->method('GET'); + $req->url->parse('http://127.0.0.1/foo/bar'); + $req->cookies( + {name => 'foo', value => 'bar'}, + {name => 'foo', value => 'baz'}, + {name => 'foo', value => 'yada'}, + {name => 'bar', value => 'foo'} + ); + my $req2 = Mojo::Message::Request->new; + $req2->parse($req->to_string); + ok $req2->is_finished, 'request is finished'; + is $req2->method, 'GET', 'right method'; + is $req2->version, '1.1', 'right version'; + is $req2->headers->host, '127.0.0.1', 'right "Host" value'; + is $req2->headers->cookie, 'foo=bar; foo=baz; foo=yada; bar=foo', 'right "Cookie" value'; + is $req2->url, '/foo/bar', 'right URL'; + is $req2->url->to_abs, 'http://127.0.0.1/foo/bar', 'right absolute URL'; + is_deeply [map { $_->value } @{$req2->every_cookie('foo')}], [qw(bar baz yada)], 'right values'; + is_deeply [map { $_->value } @{$req2->every_cookie('bar')}], ['foo'], 'right values'; +}; + +subtest 'Parse full HTTP 1.0 request with cookies and progress callback' => sub { + my $req = Mojo::Message::Request->new; + my $counter = 0; + $req->on(progress => sub { $counter++ }); + is $counter, 0, 'right count'; + ok !$req->content->is_parsing_body, 'is not parsing body'; + ok !$req->is_finished, 'request is not finished'; + $req->parse('GET /foo/bar/baz.html?fo'); + is $counter, 1, 'right count'; + ok !$req->content->is_parsing_body, 'is not parsing body'; + ok !$req->is_finished, 'request is not finished'; + $req->parse("o=13 HTTP/1.0\x0d\x0aContent"); + is $counter, 2, 'right count'; + ok !$req->content->is_parsing_body, 'is not parsing body'; + ok !$req->is_finished, 'request is not finished'; + $req->parse('-Type: text/'); + is $counter, 3, 'right count'; + ok !$req->content->is_parsing_body, 'is not parsing body'; + ok !$req->is_finished, 'request is not finished'; + $req->parse("plain\x0d\x0a"); + is $counter, 4, 'right count'; + ok !$req->content->is_parsing_body, 'is not parsing body'; + ok !$req->is_finished, 'request is not finished'; + $req->parse('Cookie: foo=bar; bar=baz'); + is $counter, 5, 'right count'; + ok !$req->content->is_parsing_body, 'is not parsing body'; + ok !$req->is_finished, 'request is not finished'; + $req->parse("\x0d\x0a"); + is $counter, 6, 'right count'; + ok !$req->content->is_parsing_body, 'is not parsing body'; + ok !$req->is_finished, 'request is not finished'; + $req->parse("Content-Length: 27\x0d\x0a\x0d\x0aHell"); + is $counter, 7, 'right count'; + ok $req->content->is_parsing_body, 'is parsing body'; + ok !$req->is_finished, 'request is not finished'; + $req->parse("o World!\n1234\nlalalala\n"); + is $counter, 8, 'right count'; + ok !$req->content->is_parsing_body, 'is not parsing body'; + ok $req->is_finished, 'request is finished'; + ok $req->is_finished, 'request is finished'; + is $req->method, 'GET', 'right method'; + is $req->version, '1.0', 'right version'; + is $req->url, '/foo/bar/baz.html?foo=13', 'right URL'; + is $req->headers->content_type, 'text/plain', 'right "Content-Type" value'; + is $req->headers->content_length, 27, 'right "Content-Length" value'; + my $cookies = $req->cookies; + is $cookies->[0]->name, 'foo', 'right name'; + is $cookies->[0]->value, 'bar', 'right value'; + is $cookies->[1]->name, 'bar', 'right name'; + is $cookies->[1]->value, 'baz', 'right value'; +}; + +subtest 'Parse and clone multipart/form-data request (changing size)' => sub { + my $req = Mojo::Message::Request->new; + $req->parse("POST /example/testform_handler HTTP/1.1\x0d\x0a"); + $req->parse("User-Agent: Mozilla/5.0\x0d\x0a"); + $req->parse('Content-Type: multipart/form-data; '); + $req->parse("boundary=----WebKitFormBoundaryi5BnD9J9zoTMiSuP\x0d\x0a"); + $req->parse("Content-Length: 318\x0d\x0aConnection: keep-alive\x0d\x0a"); + $req->parse("Host: 127.0.0.1:3000\x0d\x0a\x0d\x0a"); + $req->parse("------WebKitFormBoundaryi5BnD9J9zoTMiSuP\x0d\x0a"); + $req->parse("Content-Disposition: form-data; name=\"Vorname\"\x0a"); + $req->parse("\x0d\x0aT\x0d\x0a------WebKitFormBoundaryi5BnD9J9zoTMiSuP\x0d"); + $req->parse("\x0aContent-Disposition: form-data; name=\"Zuname\"\x0a"); + $req->parse("\x0d\x0a\x0d\x0a------WebKitFormBoundaryi5BnD9J9zoTMiSuP\x0d"); + $req->parse("\x0aContent-Disposition: form-data; name=\"Text\"\x0a"); + $req->parse("\x0d\x0a\x0d\x0a------WebKitFormBoundaryi5BnD9J9zoTMiSuP--"); + ok $req->is_finished, 'request is finished'; + is $req->method, 'POST', 'right method'; + is $req->version, '1.1', 'right version'; + is $req->url, '/example/testform_handler', 'right URL'; + is $req->headers->content_length, 318, 'right "Content-Length" value'; + is $req->param('Vorname'), 'T', 'right value'; + is $req->param('Zuname'), '', 'right value'; + is $req->param('Text'), '', 'right value'; + is $req->content->parts->[0]->asset->slurp, 'T', 'right content'; + is $req->content->leftovers, '', 'no leftovers'; + $req = Mojo::Message::Request->new->parse($req->clone->to_string); + ok $req->is_finished, 'request is finished'; + is $req->method, 'POST', 'right method'; + is $req->version, '1.1', 'right version'; + is $req->url, '/example/testform_handler', 'right URL'; + is $req->headers->content_length, 323, 'right "Content-Length" value'; + is $req->param('Vorname'), 'T', 'right value'; + is $req->param('Zuname'), '', 'right value'; + is $req->param('Text'), '', 'right value'; + is $req->content->parts->[0]->asset->slurp, 'T', 'right content'; + is $req->content->leftovers, '', 'no leftovers'; + is $req->content->get_body_chunk(322), "\x0a", 'right chunk'; + is $req->content->get_body_chunk(321), "\x0d\x0a", 'right chunk'; + is $req->content->get_body_chunk(320), "-\x0d\x0a", 'right chunk'; +}; + +subtest 'Parse multipart/form-data request with charset' => sub { + my $req = Mojo::Message::Request->new; + is $req->default_charset, 'UTF-8', 'default charset is UTF-8'; + my $yatta = 'やった'; + my $yatta_sjis = encode 'Shift_JIS', $yatta; + my $multipart + = "------1234567890\x0d\x0a" + . "Content-Disposition: form-data; name=\"$yatta_sjis\"\x0d\x0a\x0d\x0a" + . "$yatta_sjis\x0d\x0a------1234567890--"; + $req->parse("POST /example/yatta HTTP/1.1\x0d\x0a" + . "User-Agent: Mozilla/5.0\x0d\x0a" + . 'Content-Type: multipart/form-data; charset=Shift_JIS; ' + . "boundary=----1234567890\x0d\x0a" + . "Content-Length: @{[length $multipart]}\x0d\x0a" + . "Connection: keep-alive\x0d\x0a" + . "Host: 127.0.0.1:3000\x0d\x0a\x0d\x0a" + . $multipart); + ok $req->is_finished, 'request is finished'; + is $req->method, 'POST', 'right method'; + is $req->version, '1.1', 'right version'; + is $req->url, '/example/yatta', 'right URL'; + is $req->param($yatta), $yatta, 'right value'; + is $req->content->parts->[0]->asset->slurp, $yatta_sjis, 'right content'; +}; + +subtest 'WebKit multipart/form-data request' => sub { + my $req = Mojo::Message::Request->new; + $req->parse("POST /example/testform_handler HTTP/1.1\x0d\x0a"); + $req->parse("User-Agent: Mozilla/5.0\x0d\x0a"); + $req->parse('Content-Type: multipart/form-data; '); + $req->parse("boundary=----WebKitFormBoundaryi5BnD9J9zoTMiSuP\x0d\x0a"); + $req->parse("Content-Length: 323\x0d\x0aConnection: keep-alive\x0d\x0a"); + $req->parse("Host: 127.0.0.1:3000\x0d\x0a\x0d\x0a"); + $req->parse("------WebKitFormBoundaryi5BnD9J9zoTMiSuP\x0d\x0a"); + $req->parse("Content-Disposition: form-data; name=\"Vorname\"\x0d\x0a"); + $req->parse("\x0d\x0aT\x0d\x0a------WebKitFormBoundaryi5BnD9J9zoTMiSuP\x0d"); + $req->parse("\x0aContent-Disposition: form-data; name=\"Zuname\"\x0d\x0a"); + $req->parse("\x0d\x0a\x0d\x0a------WebKitFormBoundaryi5BnD9J9zoTMiSuP\x0d"); + $req->parse("\x0aContent-Disposition: form-data; name=\"Text\"\x0d\x0a"); + $req->parse("\x0d\x0a\x0d\x0a------WebKitFormBoundaryi5BnD9J9zoTMiSuP-"); + ok !$req->is_finished, 'request is not finished'; + $req->parse('-'); + ok !$req->is_finished, 'request is not finished'; + $req->parse("\x0d\x0a"); + ok $req->is_finished, 'request is finished'; + is $req->method, 'POST', 'right method'; + is $req->version, '1.1', 'right version'; + is $req->url, '/example/testform_handler', 'right URL'; + is $req->param('Vorname'), 'T', 'right value'; + is $req->param('Zuname'), '', 'right value'; + is $req->param('Text'), '', 'right value'; + is $req->content->parts->[0]->asset->slurp, 'T', 'right content'; +}; + +subtest 'Chrome 35 multipart/form-data request (with quotation marks)' => sub { + my $req = Mojo::Message::Request->new; + $req->parse("POST / HTTP/1.1\x0d\x0a"); + $req->parse("Host: 127.0.0.1:3000\x0d\x0a"); + $req->parse("Connection: keep-alive\x0d\x0a"); + $req->parse("Content-Length: 180\x0d\x0a"); + $req->parse('Accept: text/html,application/xhtml+xml,application/xml;q='); + $req->parse("0.9,image/webp,*/*;q=0.8\x0d\x0a"); + $req->parse("Origin: http://127.0.0.1:3000\x0d\x0a"); + $req->parse('User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_0) App'); + $req->parse('leWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.101 Safari'); + $req->parse("/537.36\x0d\x0a"); + $req->parse('Content-Type: multipart/form-data; boundary=----WebKitFormBoun'); + $req->parse("daryMTelhBLWA9N3KXAR\x0d\x0a"); + $req->parse("Referer: http://127.0.0.1:3000/\x0d\x0a"); + $req->parse("Accept-Encoding: gzip,deflate,sdch\x0d\x0a"); + $req->parse("Accept-Language: en-US,en;q=0.8\x0d\x0a\x0d\x0a"); + $req->parse("------WebKitFormBoundaryMTelhBLWA9N3KXAR\x0d\x0a"); + $req->parse('Content-Disposition: form-data; na'); + $req->parse('me="foo \\%22bar%22 baz\"; filename="fo\\%22o%22.txt\"'); + $req->parse("\x0d\x0a\x0d\x0atest\x0d\x0a"); + $req->parse("------WebKitFormBoundaryMTelhBLWA9N3KXAR--\x0d\x0a"); + ok $req->is_finished, 'request is finished'; + is $req->method, 'POST', 'right method'; + is $req->version, '1.1', 'right version'; + is $req->url, '/', 'right URL'; + is $req->upload('foo \\%22bar%22 baz\\')->filename, 'fo\\%22o%22.txt\\', 'right filename'; + is $req->upload('foo \\%22bar%22 baz\\')->slurp, 'test', 'right content'; +}; + +subtest 'Firefox 24 multipart/form-data request (with quotation marks)' => sub { + my $req = Mojo::Message::Request->new; + $req->parse("POST / HTTP/1.1\x0d\x0a"); + $req->parse("Host: 127.0.0.1:3000\x0d\x0a"); + $req->parse('User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9; rv:24.'); + $req->parse("0) Gecko/20100101 Firefox/24.0\x0d\x0a"); + $req->parse('Accept: text/html,application/xhtml+xml,application/xml;q=0.9'); + $req->parse(",*/*;q=0.8\x0d\x0a"); + $req->parse("Accept-Language: de-de,de;q=0.8,en-us;q=0.5,en;q=0.3\x0d\x0a"); + $req->parse("Accept-Encoding: gzip, deflate\x0d\x0a"); + $req->parse("Referer: http://127.0.0.1:3000/\x0d\x0a"); + $req->parse("Connection: keep-alive\x0d\x0a"); + $req->parse('Content-Type: multipart/form-data; boundary=-----------------'); + $req->parse("----------20773201241877674789807986058\x0d\x0a"); + $req->parse("Content-Length: 212\x0d\x0a\x0d\x0a"); + $req->parse('-----------------------------2077320124187767478980'); + $req->parse("7986058\x0d\x0aContent-Disposition: form-data; na"); + $req->parse('me="foo \\\\"bar\\" baz"; filename="fo\\\\"o\\".txt"'); + $req->parse("\x0d\x0a\x0d\x0atest\x0d\x0a"); + $req->parse('-----------------------------2077320124187767'); + $req->parse("4789807986058--\x0d\x0a"); + ok $req->is_finished, 'request is finished'; + is $req->method, 'POST', 'right method'; + is $req->version, '1.1', 'right version'; + is $req->url, '/', 'right URL'; + is $req->upload('foo \\\"bar\" baz')->filename, 'fo\\\\"o\\".txt', 'right filename'; + is $req->upload('foo \\\"bar\" baz')->slurp, 'test', 'right content'; +}; + +subtest 'Chrome 5 multipart/form-data request (UTF-8)' => sub { + my $req = Mojo::Message::Request->new; + my ($fname, $sname, $sex, $avatar, $submit) = map { encode 'UTF-8', $_ } 'Иван', 'Иванов', 'мужской', 'аватар.jpg', + 'Сохранить'; + my $chrome + = "------WebKitFormBoundaryYGjwdkpB6ZLCZQbX\x0d\x0a" + . "Content-Disposition: form-data; name=\"fname\"\x0d\x0a\x0d\x0a" + . "$fname\x0d\x0a------WebKitFormBoundaryYGjwdkpB6ZLCZQbX\x0d\x0a" + . "Content-Disposition: form-data; name=\"sname\"\x0d\x0a\x0d\x0a" + . "$sname\x0d\x0a------WebKitFormBoundaryYGjwdkpB6ZLCZQbX\x0d\x0a" + . "Content-Disposition: form-data; name=\"sex\"\x0d\x0a\x0d\x0a" + . "$sex\x0d\x0a------WebKitFormBoundaryYGjwdkpB6ZLCZQbX\x0d\x0a" + . "Content-Disposition: form-data; name=\"bdate\"\x0d\x0a\x0d\x0a" + . "16.02.1987\x0d\x0a------WebKitFormBoundaryYGjwdkpB6ZLCZQbX\x0d\x0a" + . "Content-Disposition: form-data; name=\"phone\"\x0d\x0a\x0d\x0a" + . "1234567890\x0d\x0a------WebKitFormBoundaryYGjwdkpB6ZLCZQbX\x0d\x0a" + . "Content-Disposition: form-data; name=\"avatar\"; filename=\"$avatar\"" + . "\x0d\x0aContent-Type: image/jpeg\x0d\x0a\x0d\x0a1234" + . "\x0d\x0a------WebKitFormBoundaryYGjwdkpB6ZLCZQbX\x0d\x0a" + . "Content-Disposition: form-data; name=\"submit\"\x0d\x0a\x0d\x0a" + . "$submit\x0d\x0a------WebKitFormBoundaryYGjwdkpB6ZLCZQbX--"; + $req->parse("POST / HTTP/1.0\x0d\x0a" + . "Host: 127.0.0.1:10002\x0d\x0a" + . "Connection: close\x0d\x0a" + . 'User-Agent: Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/5' + . "32.9 (KHTML, like Gecko) Chrome/5.0.307.11 Safari/532.9\x0d\x0a" + . "Referer: http://example.org/\x0d\x0a" + . "Content-Length: @{[length $chrome]}\x0d\x0a" + . "Cache-Control: max-age=0\x0d\x0a" + . "Origin: http://example.org\x0d\x0a" + . 'Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryY' + . "GjwdkpB6ZLCZQbX\x0d\x0a" + . 'Accept: application/xml,application/xhtml+xml,text/html;q=0.9,text/' + . "plain;q=0.8,image/png,*/*;q=0.5\x0d\x0a" + . "Accept-Encoding: gzip,deflate,sdch\x0d\x0a" + . 'Cookie: mojolicious=BAcIMTIzNDU2NzgECAgIAwIAAAAXDGFsZXgudm9yb25vdgQ' + . 'AAAB1c2VyBp6FjksAAAAABwAAAGV4cGlyZXM=--1641adddfe885276cda0deb7475f' + . "153a\x0d\x0a" + . "Accept-Language: ru-RU,ru;q=0.8,en-US;q=0.6,en;q=0.4\x0d\x0a" + . "Accept-Charset: windows-1251,utf-8;q=0.7,*;q=0.3\x0d\x0a\x0d\x0a" + . $chrome); + ok $req->is_finished, 'request is finished'; + is $req->method, 'POST', 'right method'; + is $req->version, '1.0', 'right version'; + is $req->url, '/', 'right URL'; + is $req->cookie('mojolicious')->value, 'BAcIMTIzNDU2NzgECAgIAwIAAAAXDGFsZXgudm9yb25vdgQAAAB1c2VyBp6FjksAAAAABwA' + . 'AAGV4cGlyZXM=--1641adddfe885276cda0deb7475f153a', 'right value'; + like $req->headers->content_type, qr!multipart/form-data!, 'right "Content-Type" value'; + is $req->param('fname'), 'Иван', 'right value'; + is $req->param('sname'), 'Иванов', 'right value'; + is $req->param('sex'), 'мужской', 'right value'; + is $req->param('bdate'), '16.02.1987', 'right value'; + is $req->param('phone'), '1234567890', 'right value'; + is $req->param('submit'), 'Сохранить', 'right value'; + my $upload = $req->upload('avatar'); + is $upload->isa('Mojo::Upload'), 1, 'right upload'; + is $upload->headers->content_type, 'image/jpeg', 'right "Content-Type" value'; + is $upload->filename, 'аватар.jpg', 'right filename'; + is $upload->size, 4, 'right size'; + is $upload->slurp, '1234', 'right content'; + is $req->content->parts->[0]->asset->slurp, $fname, 'right content'; +}; + +subtest 'Firefox 3.5.8 multipart/form-data request (UTF-8)' => sub { + my $req = Mojo::Message::Request->new; + my ($fname, $sname, $sex, $avatar, $submit) = map { encode 'UTF-8', $_ } 'Иван', 'Иванов', 'мужской', 'аватар.jpg', + 'Сохранить'; + my $firefox + = "-----------------------------213090722714721300002030499922\x0d\x0a" + . "Content-Disposition: form-data; name=\"fname\"\x0d\x0a\x0d\x0a" + . "$fname\x0d\x0a" + . "-----------------------------213090722714721300002030499922\x0d\x0a" + . "Content-Disposition: form-data; name=\"sname\"\x0d\x0a\x0d\x0a" + . "$sname\x0d\x0a" + . "-----------------------------213090722714721300002030499922\x0d\x0a" + . "Content-Disposition: form-data; name=\"sex\"\x0d\x0a\x0d\x0a" + . "$sex\x0d\x0a" + . "-----------------------------213090722714721300002030499922\x0d\x0a" + . "Content-Disposition: form-data; name=\"bdate\"\x0d\x0a\x0d\x0a" + . "16.02.1987\x0d\x0a" + . "-----------------------------213090722714721300002030499922\x0d\x0a" + . "Content-Disposition: form-data; name=\"phone\"\x0d\x0a\x0d\x0a" + . "1234567890\x0d\x0a" + . "-----------------------------213090722714721300002030499922\x0d\x0a" + . "Content-Disposition: form-data; name=\"avatar\"; filename=\"$avatar\"" + . "\x0d\x0aContent-Type: image/jpeg\x0d\x0a\x0d\x0a1234\x0d\x0a" + . "-----------------------------213090722714721300002030499922\x0d\x0a" + . "Content-Disposition: form-data; name=\"submit\"\x0d\x0a\x0d\x0a" + . "$submit\x0d\x0a" + . '-----------------------------213090722714721300002030499922--'; + $req->parse("POST / HTTP/1.0\x0d\x0a" + . "Host: 127.0.0.1:10002\x0d\x0a" + . "Connection: close\x0d\x0a" + . 'User-Agent: Mozilla/5.0 (X11; U; Linux x86_64; ru; rv:1.9.1.8) Geck' + . "o/20100214 Ubuntu/9.10 (karmic) Firefox/3.5.8\x0d\x0a" + . 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q' + . "=0.8\x0d\x0a" + . "Accept-Language: ru,en-us;q=0.7,en;q=0.3\x0d\x0a" + . "Accept-Encoding: gzip,deflate\x0d\x0a" + . "Accept-Charset: windows-1251,utf-8;q=0.7,*;q=0.7\x0d\x0a" + . "Referer: http://example.org/\x0d\x0a" + . 'Cookie: mojolicious=BAcIMTIzNDU2NzgECAgIAwIAAAAXDGFsZXgudm9yb25vdgQ' + . 'AAAB1c2VyBiWFjksAAAAABwAAAGV4cGlyZXM=--cd933a37999e0fa8d7804205e891' + . "93a7\x0d\x0a" + . 'Content-Type: multipart/form-data; boundary=-----------------------' + . "----213090722714721300002030499922\x0d\x0a" + . "Content-Length: @{[length $firefox]}\x0d\x0a\x0d\x0a" + . $firefox); + ok $req->is_finished, 'request is finished'; + is $req->method, 'POST', 'right method'; + is $req->version, '1.0', 'right version'; + is $req->url, '/', 'right URL'; + is $req->cookie('mojolicious')->value, 'BAcIMTIzNDU2NzgECAgIAwIAAAAXDGFsZXgudm9yb25vdgQAAAB1c2VyBiWFjksAAAAABwA' + . 'AAGV4cGlyZXM=--cd933a37999e0fa8d7804205e89193a7', 'right value'; + like $req->headers->content_type, qr!multipart/form-data!, 'right "Content-Type" value'; + is $req->param('fname'), 'Иван', 'right value'; + is $req->param('sname'), 'Иванов', 'right value'; + is $req->param('sex'), 'мужской', 'right value'; + is $req->param('bdate'), '16.02.1987', 'right value'; + is $req->param('phone'), '1234567890', 'right value'; + is $req->param('submit'), 'Сохранить', 'right value'; + my $upload = $req->upload('avatar'); + is $upload->isa('Mojo::Upload'), 1, 'right upload'; + is $upload->headers->content_type, 'image/jpeg', 'right "Content-Type" value'; + is $upload->filename, 'аватар.jpg', 'right filename'; + is $upload->size, 4, 'right size'; + is $upload->slurp, '1234', 'right content'; + is $req->content->parts->[0]->asset->slurp, $fname, 'right content'; +}; + +subtest 'Opera 9.8 multipart/form-data request (UTF-8)' => sub { + my $req = Mojo::Message::Request->new; + my ($fname, $sname, $sex, $avatar, $submit) = map { encode 'UTF-8', $_ } 'Иван', 'Иванов', 'мужской', 'аватар.jpg', + 'Сохранить'; + my $opera + = "------------IWq9cR9mYYG668xwSn56f0\x0d\x0a" + . "Content-Disposition: form-data; name=\"fname\"\x0d\x0a\x0d\x0a" + . "$fname\x0d\x0a------------IWq9cR9mYYG668xwSn56f0\x0d\x0a" + . "Content-Disposition: form-data; name=\"sname\"\x0d\x0a\x0d\x0a" + . "$sname\x0d\x0a------------IWq9cR9mYYG668xwSn56f0\x0d\x0a" + . "Content-Disposition: form-data; name=\"sex\"\x0d\x0a\x0d\x0a" + . "$sex\x0d\x0a------------IWq9cR9mYYG668xwSn56f0\x0d\x0a" + . "Content-Disposition: form-data; name=\"bdate\"\x0d\x0a\x0d\x0a" + . "16.02.1987\x0d\x0a------------IWq9cR9mYYG668xwSn56f0\x0d\x0a" + . "Content-Disposition: form-data; name=\"phone\"\x0d\x0a\x0d\x0a" + . "1234567890\x0d\x0a------------IWq9cR9mYYG668xwSn56f0\x0d\x0a" + . "Content-Disposition: form-data; name=\"avatar\"; filename=\"$avatar\"" + . "\x0d\x0aContent-Type: image/jpeg\x0d\x0a\x0d\x0a" + . "1234\x0d\x0a------------IWq9cR9mYYG668xwSn56f0\x0d\x0a" + . "Content-Disposition: form-data; name=\"submit\"\x0d\x0a\x0d\x0a" + . "$submit\x0d\x0a------------IWq9cR9mYYG668xwSn56f0--"; + $req->parse("POST / HTTP/1.0\x0d\x0a" + . "Host: 127.0.0.1:10002\x0d\x0a" + . "Connection: close\x0d\x0a" + . 'User-Agent: Opera/9.80 (X11; Linux x86_64; U; ru) Presto/2.2.15 Ver' + . "sion/10.10\x0d\x0a" + . 'Accept: text/html, application/xml;q=0.9, application/xhtml+xml, im' + . "age/png, image/jpeg, image/gif, image/x-xbitmap, */*;q=0.1\x0d\x0a" + . "Accept-Language: ru-RU,ru;q=0.9,en;q=0.8\x0d\x0a" + . "Accept-Charset: iso-8859-1, utf-8, utf-16, *;q=0.1\x0d\x0a" + . "Accept-Encoding: deflate, gzip, x-gzip, identity, *;q=0\x0d\x0a" + . "Referer: http://example.org/\x0d\x0a" + . 'Cookie: mojolicious=BAcIMTIzNDU2NzgECAgIAwIAAAAXDGFsZXgudm9yb25vdgQ' + . 'AAAB1c2VyBhaIjksAAAAABwAAAGV4cGlyZXM=--78a58a94f98ae5b75a489be1189f' + . "2672\x0d\x0a" + . "Cookie2: \$Version=1\x0d\x0a" + . "TE: deflate, gzip, chunked, identity, trailers\x0d\x0a" + . "Content-Length: @{[length $opera]}\x0d\x0a" + . 'Content-Type: multipart/form-data; boundary=----------IWq9cR9mYYG66' + . "8xwSn56f0\x0d\x0a\x0d\x0a" + . $opera); + ok $req->is_finished, 'request is finished'; + is $req->method, 'POST', 'right method'; + is $req->version, '1.0', 'right version'; + is $req->url, '/', 'right URL'; + is $req->cookie('mojolicious')->value, 'BAcIMTIzNDU2NzgECAgIAwIAAAAXDGFsZXgudm9yb25vdgQAAAB1c2VyBhaIjksAAAAABwA' + . 'AAGV4cGlyZXM=--78a58a94f98ae5b75a489be1189f2672', 'right value'; + like $req->headers->content_type, qr!multipart/form-data!, 'right "Content-Type" value'; + is $req->param('fname'), 'Иван', 'right value'; + is $req->param('sname'), 'Иванов', 'right value'; + is $req->param('sex'), 'мужской', 'right value'; + is $req->param('bdate'), '16.02.1987', 'right value'; + is $req->param('phone'), '1234567890', 'right value'; + is $req->param('submit'), 'Сохранить', 'right value'; + my $upload = $req->upload('avatar'); + is $upload->isa('Mojo::Upload'), 1, 'right upload'; + is $upload->headers->content_type, 'image/jpeg', 'right "Content-Type" value'; + is $upload->filename, 'аватар.jpg', 'right filename'; + is $upload->size, 4, 'right size'; + is $upload->slurp, '1234', 'right content'; + is $req->content->parts->[0]->asset->slurp, $fname, 'right content'; +}; + +subtest 'Firefox 14 multipart/form-data request (UTF-8)' => sub { + my $req = Mojo::Message::Request->new; + my ($name, $filename) = map { encode 'UTF-8', $_ } '☃', 'foo bär ☃.txt'; + my $firefox + = "-----------------------------1264369278903768281481663536\x0d\x0a" + . "Content-Disposition: form-data; name=\"$name\"; filename=\"$filename\"" + . "\x0d\x0aContent-Type: text/plain\x0d\x0a\x0d\x0atest 123\x0d\x0a" + . '-----------------------------1264369278903768281481663536--'; + $req->parse("POST /foo HTTP/1.1\x0d\x0a"); + $req->parse("Host: 127.0.0.1:3000\x0d\x0a"); + $req->parse('User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv'); + $req->parse(":14.0) Gecko/20100101 Firefox/14.0.1\x0d\x0a"); + $req->parse('Accept: text/html,application/xhtml+xml,application/xml;'); + $req->parse("q=0.9,*/*;q=0.8\x0d\x0a"); + $req->parse("Accept-Language: en-us,en;q=0.5\x0d\x0a"); + $req->parse("Accept-Encoding: gzip, deflate\x0d\x0a"); + $req->parse("Connection: keep-alive\x0d\x0a"); + $req->parse("Referer: http://127.0.0.1:3000/\x0d\x0a"); + $req->parse('Content-Type: multipart/form-data;'); + $req->parse('boundary=---------------------------126436927890376828148'); + $req->parse("1663536\x0d\x0a"); + $req->parse("Content-Length: @{[length $firefox]}\x0d\x0a\x0d\x0a"); + $req->parse($firefox); + ok $req->is_finished, 'request is finished'; + is $req->method, 'POST', 'right method'; + is $req->version, '1.1', 'right version'; + is $req->url, '/foo', 'right URL'; + like $req->headers->content_type, qr!multipart/form-data!, 'right "Content-Type" value'; + is $req->upload('☃')->name, '☃', 'right name'; + is $req->upload('☃')->filename, 'foo bär ☃.txt', 'right filename'; + is $req->upload('☃')->headers->content_type, 'text/plain', 'right "Content-Type" value'; + is $req->upload('☃')->asset->size, 8, 'right size'; + is $req->upload('☃')->asset->slurp, 'test 123', 'right content'; +}; + +subtest 'Parse "~" in URL' => sub { + my $req = Mojo::Message::Request->new; + $req->parse("GET /~foobar/ HTTP/1.1\x0d\x0a\x0d\x0a"); + ok $req->is_finished, 'request is finished'; + is $req->method, 'GET', 'right method'; + is $req->version, '1.1', 'right version'; + is $req->url, '/~foobar/', 'right URL'; +}; + +subtest 'Parse ":" in URL' => sub { + my $req = Mojo::Message::Request->new; + $req->parse("GET /perldoc?Mojo::Message::Request HTTP/1.1\x0d\x0a\x0d\x0a"); + ok $req->is_finished, 'request is finished'; + is $req->method, 'GET', 'right method'; + is $req->version, '1.1', 'right version'; + is $req->url, '/perldoc?Mojo::Message::Request', 'right URL'; + is $req->url->query->pairs->[0], 'Mojo::Message::Request', 'right value'; +}; + +subtest 'Parse lots of special characters in URL' => sub { + my $req = Mojo::Message::Request->new; + $req->parse("GET /09azAZ!\$%&'()*+,-./:;=?@[\\]^_`{|}~\xC3\x9F#abc "); + $req->parse("HTTP/1.1\x0d\x0a\x0d\x0a"); + ok $req->is_finished, 'request is finished'; + is $req->method, 'GET', 'right method'; + is $req->version, '1.1', 'right version'; + is $req->url, "/09azAZ!\$%&'()*+,-./:;=?@%5B%5C%5D%5E_%60%7B%7C%7D~%C3%83%C2%9F", 'right URL'; +}; + +subtest 'Abstract methods' => sub { + eval { Mojo::Message->cookies }; + like $@, qr/Method "cookies" not implemented by subclass/, 'right error'; + eval { Mojo::Message->extract_start_line }; + like $@, qr/Method "extract_start_line" not implemented by subclass/, 'right error'; + eval { Mojo::Message->get_start_line_chunk }; + like $@, qr/Method "get_start_line_chunk" not implemented by subclass/, 'right error'; + eval { Mojo::Message->start_line_size }; + like $@, qr/Method "start_line_size" not implemented by subclass/, 'right error'; +}; done_testing(); diff --git a/t/mojo/request_cgi.t b/t/mojo/request_cgi.t index 40c9bb7231..587eff19d3 100644 --- a/t/mojo/request_cgi.t +++ b/t/mojo/request_cgi.t @@ -3,96 +3,99 @@ use Mojo::Base -strict; use Test::More; use Mojo::Message::Request; -# Parse Lighttpd CGI environment variables and body -my $req = Mojo::Message::Request->new; -my $body; -$req->content->on(body => sub { $body++ }); -$req->parse({ - HTTP_CONTENT_LENGTH => 11, - HTTP_DNT => 1, - PATH_INFO => '/te+st/index.cgi/foo/bar', - QUERY_STRING => 'lalala=23&bar=baz', - REQUEST_METHOD => 'POST', - SCRIPT_NAME => '/te+st/index.cgi', - HTTP_HOST => 'localhost:8080', - SERVER_PROTOCOL => 'HTTP/1.0' -}); -is $body, 1, 'body event has been emitted once'; -$req->parse('Hello '); -is $body, 1, 'body event has been emitted once'; -$req->parse('World'); -is $body, 1, 'body event has been emitted once'; -ok $req->is_finished, 'request is finished'; -is $req->method, 'POST', 'right method'; -is $req->url->path, 'foo/bar', 'right path'; -is $req->url->base->path, '/te+st/index.cgi/', 'right base path'; -is $req->url->base->host, 'localhost', 'right base host'; -is $req->url->base->port, 8080, 'right base port'; -is $req->url->query, 'lalala=23&bar=baz', 'right query'; -is $req->version, '1.0', 'right version'; -is $req->headers->dnt, 1, 'right "DNT" value'; -is $req->body, 'Hello World', 'right content'; -is $req->url->to_abs->to_string, 'http://localhost:8080/te+st/index.cgi/foo/bar?lalala=23&bar=baz', - 'right absolute URL'; +subtest 'Parse Lighttpd CGI environment variables and body' => sub { + my $req = Mojo::Message::Request->new; + my $body; + $req->content->on(body => sub { $body++ }); + $req->parse({ + HTTP_CONTENT_LENGTH => 11, + HTTP_DNT => 1, + PATH_INFO => '/te+st/index.cgi/foo/bar', + QUERY_STRING => 'lalala=23&bar=baz', + REQUEST_METHOD => 'POST', + SCRIPT_NAME => '/te+st/index.cgi', + HTTP_HOST => 'localhost:8080', + SERVER_PROTOCOL => 'HTTP/1.0' + }); + is $body, 1, 'body event has been emitted once'; + $req->parse('Hello '); + is $body, 1, 'body event has been emitted once'; + $req->parse('World'); + is $body, 1, 'body event has been emitted once'; + ok $req->is_finished, 'request is finished'; + is $req->method, 'POST', 'right method'; + is $req->url->path, 'foo/bar', 'right path'; + is $req->url->base->path, '/te+st/index.cgi/', 'right base path'; + is $req->url->base->host, 'localhost', 'right base host'; + is $req->url->base->port, 8080, 'right base port'; + is $req->url->query, 'lalala=23&bar=baz', 'right query'; + is $req->version, '1.0', 'right version'; + is $req->headers->dnt, 1, 'right "DNT" value'; + is $req->body, 'Hello World', 'right content'; + is $req->url->to_abs->to_string, 'http://localhost:8080/te+st/index.cgi/foo/bar?lalala=23&bar=baz', + 'right absolute URL'; +}; -# Parse Lighttpd CGI environment variables and body (behind reverse proxy) -$req = Mojo::Message::Request->new; -$req->parse({ - HTTP_CONTENT_LENGTH => 11, - HTTP_DNT => 1, - HTTP_X_FORWARDED_FOR => '127.0.0.1', - PATH_INFO => '/test/index.cgi/foo/bar', - QUERY_STRING => 'lalala=23&bar=baz', - REQUEST_METHOD => 'POST', - SCRIPT_NAME => '/test/index.cgi', - HTTP_HOST => 'mojolicious.org', - SERVER_PROTOCOL => 'HTTP/1.0' -}); -$req->parse('Hello World'); -ok $req->is_finished, 'request is finished'; -is $req->method, 'POST', 'right method'; -is $req->url->path, 'foo/bar', 'right path'; -is $req->url->base->path, '/test/index.cgi/', 'right base path'; -is $req->url->base->host, 'mojolicious.org', 'right base host'; -is $req->url->base->port, undef, 'no base port'; -is $req->url->query, 'lalala=23&bar=baz', 'right query'; -is $req->version, '1.0', 'right version'; -is $req->headers->dnt, 1, 'right "DNT" value'; -is $req->body, 'Hello World', 'right content'; -is $req->url->to_abs->to_string, 'http://mojolicious.org/test/index.cgi/foo/bar?lalala=23&bar=baz', - 'right absolute URL'; +subtest 'Parse Lighttpd CGI environment variables and body (behind reverse proxy)' => sub { + my $req = Mojo::Message::Request->new; + $req->parse({ + HTTP_CONTENT_LENGTH => 11, + HTTP_DNT => 1, + HTTP_X_FORWARDED_FOR => '127.0.0.1', + PATH_INFO => '/test/index.cgi/foo/bar', + QUERY_STRING => 'lalala=23&bar=baz', + REQUEST_METHOD => 'POST', + SCRIPT_NAME => '/test/index.cgi', + HTTP_HOST => 'mojolicious.org', + SERVER_PROTOCOL => 'HTTP/1.0' + }); + $req->parse('Hello World'); + ok $req->is_finished, 'request is finished'; + is $req->method, 'POST', 'right method'; + is $req->url->path, 'foo/bar', 'right path'; + is $req->url->base->path, '/test/index.cgi/', 'right base path'; + is $req->url->base->host, 'mojolicious.org', 'right base host'; + is $req->url->base->port, undef, 'no base port'; + is $req->url->query, 'lalala=23&bar=baz', 'right query'; + is $req->version, '1.0', 'right version'; + is $req->headers->dnt, 1, 'right "DNT" value'; + is $req->body, 'Hello World', 'right content'; + is $req->url->to_abs->to_string, 'http://mojolicious.org/test/index.cgi/foo/bar?lalala=23&bar=baz', + 'right absolute URL'; +}; -# Parse Apache CGI environment variables and body -$req = Mojo::Message::Request->new; -$req->parse({ - CONTENT_LENGTH => 11, - CONTENT_TYPE => 'application/x-www-form-urlencoded', - HTTP_DNT => 1, - PATH_INFO => '/test/index.cgi/foo/bar', - QUERY_STRING => 'lalala=23&bar=baz', - REQUEST_METHOD => 'POST', - SCRIPT_NAME => '/test/index.cgi', - HTTP_HOST => 'localhost:8080', - SERVER_PROTOCOL => 'HTTP/1.0' -}); -$req->parse('hello=world'); -ok $req->is_finished, 'request is finished'; -is $req->method, 'POST', 'right method'; -is $req->url->path, 'foo/bar', 'right path'; -is $req->url->base->path, '/test/index.cgi/', 'right base path'; -is $req->url->base->host, 'localhost', 'right base host'; -is $req->url->base->port, 8080, 'right base port'; -is $req->url->query, 'lalala=23&bar=baz', 'right query'; -is $req->version, '1.0', 'right version'; -is $req->headers->dnt, 1, 'right "DNT" value'; -is $req->body, 'hello=world', 'right content'; -is_deeply $req->param('hello'), 'world', 'right value'; -is $req->url->to_abs->to_string, 'http://localhost:8080/test/index.cgi/foo/bar?lalala=23&bar=baz', 'right absolute URL'; +subtest 'Parse Apache CGI environment variables and body' => sub { + my $req = Mojo::Message::Request->new; + $req->parse({ + CONTENT_LENGTH => 11, + CONTENT_TYPE => 'application/x-www-form-urlencoded', + HTTP_DNT => 1, + PATH_INFO => '/test/index.cgi/foo/bar', + QUERY_STRING => 'lalala=23&bar=baz', + REQUEST_METHOD => 'POST', + SCRIPT_NAME => '/test/index.cgi', + HTTP_HOST => 'localhost:8080', + SERVER_PROTOCOL => 'HTTP/1.0' + }); + $req->parse('hello=world'); + ok $req->is_finished, 'request is finished'; + is $req->method, 'POST', 'right method'; + is $req->url->path, 'foo/bar', 'right path'; + is $req->url->base->path, '/test/index.cgi/', 'right base path'; + is $req->url->base->host, 'localhost', 'right base host'; + is $req->url->base->port, 8080, 'right base port'; + is $req->url->query, 'lalala=23&bar=baz', 'right query'; + is $req->version, '1.0', 'right version'; + is $req->headers->dnt, 1, 'right "DNT" value'; + is $req->body, 'hello=world', 'right content'; + is_deeply $req->param('hello'), 'world', 'right value'; + is $req->url->to_abs->to_string, 'http://localhost:8080/test/index.cgi/foo/bar?lalala=23&bar=baz', + 'right absolute URL'; +}; -# Parse Apache CGI environment variables and body (file storage) -{ +subtest 'Parse Apache CGI environment variables and body (file storage)' => sub { local $ENV{MOJO_MAX_MEMORY_SIZE} = 10; - $req = Mojo::Message::Request->new; + my $req = Mojo::Message::Request->new; is $req->content->asset->max_memory_size, 10, 'right size'; ok !$req->content->is_parsing_body, 'is not parsing body'; $req->parse({ @@ -131,341 +134,353 @@ is $req->url->to_abs->to_string, 'http://localhost:8080/test/index.cgi/foo/bar?l is $req->body, 'Hello World!', 'right content'; is $req->url->to_abs->to_string, 'http://localhost:8080/test/index.cgi/foo/bar?lalala=23&bar=baz', 'right absolute URL'; -} +}; -# Parse Apache CGI environment variables with basic authentication -$req = Mojo::Message::Request->new; -$req->parse({ - CONTENT_LENGTH => 11, - HTTP_Authorization => 'Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==', - HTTP_Proxy_Authorization => 'Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==', - CONTENT_TYPE => 'application/x-www-form-urlencoded', - HTTP_DNT => 1, - PATH_INFO => '/test/index.cgi/foo/bar', - QUERY_STRING => 'lalala=23&bar=baz', - REQUEST_METHOD => 'POST', - SCRIPT_NAME => '/test/index.cgi', - HTTP_HOST => 'localhost:8080', - SERVER_PROTOCOL => 'HTTP/1.0' -}); -$req->parse('hello=world'); -ok $req->is_finished, 'request is finished'; -is $req->method, 'POST', 'right method'; -is $req->url->path, 'foo/bar', 'right path'; -is $req->url->base->path, '/test/index.cgi/', 'right base path'; -is $req->url->base->host, 'localhost', 'right base host'; -is $req->url->base->port, 8080, 'right base port'; -is $req->url->query, 'lalala=23&bar=baz', 'right query'; -is $req->version, '1.0', 'right version'; -is $req->headers->dnt, 1, 'right "DNT" value'; -is $req->body, 'hello=world', 'right content'; -is_deeply $req->param('hello'), 'world', 'right value'; -is $req->url->to_abs->to_string, 'http://localhost:8080/test/index.cgi/foo/bar?lalala=23&bar=baz', 'right absolute URL'; -is $req->url->base, 'http://localhost:8080/test/index.cgi/', 'right base URL'; -is $req->url->base->userinfo, 'Aladdin:open sesame', 'right userinfo'; -is $req->url, 'foo/bar?lalala=23&bar=baz', 'right URL'; -is $req->proxy->userinfo, 'Aladdin:open sesame', 'right proxy userinfo'; +subtest 'Parse Apache CGI environment variables with basic authentication' => sub { + my $req = Mojo::Message::Request->new; + $req->parse({ + CONTENT_LENGTH => 11, + HTTP_Authorization => 'Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==', + HTTP_Proxy_Authorization => 'Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==', + CONTENT_TYPE => 'application/x-www-form-urlencoded', + HTTP_DNT => 1, + PATH_INFO => '/test/index.cgi/foo/bar', + QUERY_STRING => 'lalala=23&bar=baz', + REQUEST_METHOD => 'POST', + SCRIPT_NAME => '/test/index.cgi', + HTTP_HOST => 'localhost:8080', + SERVER_PROTOCOL => 'HTTP/1.0' + }); + $req->parse('hello=world'); + ok $req->is_finished, 'request is finished'; + is $req->method, 'POST', 'right method'; + is $req->url->path, 'foo/bar', 'right path'; + is $req->url->base->path, '/test/index.cgi/', 'right base path'; + is $req->url->base->host, 'localhost', 'right base host'; + is $req->url->base->port, 8080, 'right base port'; + is $req->url->query, 'lalala=23&bar=baz', 'right query'; + is $req->version, '1.0', 'right version'; + is $req->headers->dnt, 1, 'right "DNT" value'; + is $req->body, 'hello=world', 'right content'; + is_deeply $req->param('hello'), 'world', 'right value'; + is $req->url->to_abs->to_string, 'http://localhost:8080/test/index.cgi/foo/bar?lalala=23&bar=baz', + 'right absolute URL'; + is $req->url->base, 'http://localhost:8080/test/index.cgi/', 'right base URL'; + is $req->url->base->userinfo, 'Aladdin:open sesame', 'right userinfo'; + is $req->url, 'foo/bar?lalala=23&bar=baz', 'right URL'; + is $req->proxy->userinfo, 'Aladdin:open sesame', 'right proxy userinfo'; +}; -# Parse Apache 2.2 (win32) CGI environment variables and body -$req = Mojo::Message::Request->new; -my ($finished, $progress); -$req->on(finish => sub { $finished = shift->is_finished }); -$req->on(progress => sub { $progress++ }); -ok !$finished, 'not finished'; -ok !$progress, 'no progress'; -is $req->content->progress, 0, 'right progress'; -$req->parse({ - CONTENT_LENGTH => 87, - CONTENT_TYPE => 'application/x-www-form-urlencoded; charset=UTF-8', - PATH_INFO => '', - QUERY_STRING => '', - REQUEST_METHOD => 'POST', - SCRIPT_NAME => '/index.pl', - HTTP_HOST => 'test1', - SERVER_PROTOCOL => 'HTTP/1.1' -}); -ok !$finished, 'not finished'; -ok $progress, 'made progress'; -$progress = 0; -is $req->content->progress, 0, 'right progress'; -$req->parse('request=&ajax=true&login=test&password=111&'); -ok !$finished, 'not finished'; -ok $progress, 'made progress'; -$progress = 0; -is $req->content->progress, 43, 'right progress'; -$req->parse('edition=db6d8b30-16df-4ecd-be2f-c8194f94e1f4'); -ok $finished, 'finished'; -ok $progress, 'made progress'; -is $req->content->progress, 87, 'right progress'; -ok $req->is_finished, 'request is finished'; -is $req->method, 'POST', 'right method'; -is $req->url->path, '', 'no path'; -is $req->url->base->path, '/index.pl/', 'right base path'; -is $req->url->base->host, 'test1', 'right base host'; -is $req->url->base->port, undef, 'no base port'; -ok !$req->url->query->to_string, 'no query'; -is $req->version, '1.1', 'right version'; -is $req->body, 'request=&ajax=true&login=test&password=111&' . 'edition=db6d8b30-16df-4ecd-be2f-c8194f94e1f4', - 'right content'; -is $req->param('ajax'), 'true', 'right value'; -is $req->param('login'), 'test', 'right value'; -is $req->param('password'), '111', 'right value'; -is $req->param('edition'), 'db6d8b30-16df-4ecd-be2f-c8194f94e1f4', 'right value'; -is $req->url->to_abs->to_string, 'http://test1/index.pl/', 'right absolute URL'; +subtest 'Parse Apache 2.2 (win32) CGI environment variables and body' => sub { + my $req = Mojo::Message::Request->new; + my ($finished, $progress); + $req->on(finish => sub { $finished = shift->is_finished }); + $req->on(progress => sub { $progress++ }); + ok !$finished, 'not finished'; + ok !$progress, 'no progress'; + is $req->content->progress, 0, 'right progress'; + $req->parse({ + CONTENT_LENGTH => 87, + CONTENT_TYPE => 'application/x-www-form-urlencoded; charset=UTF-8', + PATH_INFO => '', + QUERY_STRING => '', + REQUEST_METHOD => 'POST', + SCRIPT_NAME => '/index.pl', + HTTP_HOST => 'test1', + SERVER_PROTOCOL => 'HTTP/1.1' + }); + ok !$finished, 'not finished'; + ok $progress, 'made progress'; + $progress = 0; + is $req->content->progress, 0, 'right progress'; + $req->parse('request=&ajax=true&login=test&password=111&'); + ok !$finished, 'not finished'; + ok $progress, 'made progress'; + $progress = 0; + is $req->content->progress, 43, 'right progress'; + $req->parse('edition=db6d8b30-16df-4ecd-be2f-c8194f94e1f4'); + ok $finished, 'finished'; + ok $progress, 'made progress'; + is $req->content->progress, 87, 'right progress'; + ok $req->is_finished, 'request is finished'; + is $req->method, 'POST', 'right method'; + is $req->url->path, '', 'no path'; + is $req->url->base->path, '/index.pl/', 'right base path'; + is $req->url->base->host, 'test1', 'right base host'; + is $req->url->base->port, undef, 'no base port'; + ok !$req->url->query->to_string, 'no query'; + is $req->version, '1.1', 'right version'; + is $req->body, 'request=&ajax=true&login=test&password=111&' . 'edition=db6d8b30-16df-4ecd-be2f-c8194f94e1f4', + 'right content'; + is $req->param('ajax'), 'true', 'right value'; + is $req->param('login'), 'test', 'right value'; + is $req->param('password'), '111', 'right value'; + is $req->param('edition'), 'db6d8b30-16df-4ecd-be2f-c8194f94e1f4', 'right value'; + is $req->url->to_abs->to_string, 'http://test1/index.pl/', 'right absolute URL'; +}; -# Parse Apache 2.2 (win32) CGI environment variables and body -$req = Mojo::Message::Request->new; -$req->parse({ - CONTENT_LENGTH => 87, - CONTENT_TYPE => 'application/x-www-form-urlencoded; charset=UTF-8', - PATH_INFO => '', - QUERY_STRING => '', - REQUEST_METHOD => 'POST', - SCRIPT_NAME => '/index.pl', - HTTP_HOST => 'test1', - SERVER_PROTOCOL => 'HTTP/1.1' -}); -$req->parse('request=&ajax=true&login=test&password=111&'); -$req->parse('edition=db6d8b30-16df-4ecd-be2f-c8194f94e1f4'); -ok $req->is_finished, 'request is finished'; -is $req->method, 'POST', 'right method'; -is $req->url->path, '', 'no path'; -is $req->url->base->path, '/index.pl/', 'right base path'; -is $req->url->base->host, 'test1', 'right base host'; -is $req->url->base->port, undef, 'no base port'; -ok !$req->url->query->to_string, 'no query'; -is $req->version, '1.1', 'right version'; -is $req->body, 'request=&ajax=true&login=test&password=111&' . 'edition=db6d8b30-16df-4ecd-be2f-c8194f94e1f4', - 'right content'; -is $req->param('ajax'), 'true', 'right value'; -is $req->param('login'), 'test', 'right value'; -is $req->param('password'), '111', 'right value'; -is $req->param('edition'), 'db6d8b30-16df-4ecd-be2f-c8194f94e1f4', 'right value'; -is $req->url->to_abs->to_string, 'http://test1/index.pl/', 'right absolute URL'; +subtest 'Parse Apache 2.2 (win32) CGI environment variables and body' => sub { + my $req = Mojo::Message::Request->new; + $req->parse({ + CONTENT_LENGTH => 87, + CONTENT_TYPE => 'application/x-www-form-urlencoded; charset=UTF-8', + PATH_INFO => '', + QUERY_STRING => '', + REQUEST_METHOD => 'POST', + SCRIPT_NAME => '/index.pl', + HTTP_HOST => 'test1', + SERVER_PROTOCOL => 'HTTP/1.1' + }); + $req->parse('request=&ajax=true&login=test&password=111&'); + $req->parse('edition=db6d8b30-16df-4ecd-be2f-c8194f94e1f4'); + ok $req->is_finished, 'request is finished'; + is $req->method, 'POST', 'right method'; + is $req->url->path, '', 'no path'; + is $req->url->base->path, '/index.pl/', 'right base path'; + is $req->url->base->host, 'test1', 'right base host'; + is $req->url->base->port, undef, 'no base port'; + ok !$req->url->query->to_string, 'no query'; + is $req->version, '1.1', 'right version'; + is $req->body, 'request=&ajax=true&login=test&password=111&' . 'edition=db6d8b30-16df-4ecd-be2f-c8194f94e1f4', + 'right content'; + is $req->param('ajax'), 'true', 'right value'; + is $req->param('login'), 'test', 'right value'; + is $req->param('password'), '111', 'right value'; + is $req->param('edition'), 'db6d8b30-16df-4ecd-be2f-c8194f94e1f4', 'right value'; + is $req->url->to_abs->to_string, 'http://test1/index.pl/', 'right absolute URL'; +}; -# Parse Apache 2.2.14 CGI environment variables and body (root) -$req = Mojo::Message::Request->new; -$req->parse({ - SCRIPT_NAME => '/upload', - SERVER_NAME => '127.0.0.1', - SERVER_ADMIN => '[no address given]', - PATH_INFO => '/upload', - HTTP_CONNECTION => 'Keep-Alive', - REQUEST_METHOD => 'POST', - CONTENT_LENGTH => '11', - SCRIPT_FILENAME => '/tmp/SnLu1cQ3t2/test.fcgi', - SERVER_SOFTWARE => 'Apache/2.2.14 (Unix) mod_fastcgi/2.4.2', - QUERY_STRING => '', - REMOTE_PORT => '58232', - HTTP_USER_AGENT => 'Mojolicious (Perl)', - SERVER_PORT => '13028', - SERVER_SIGNATURE => '', - REMOTE_ADDR => '127.0.0.1', - CONTENT_TYPE => 'application/x-www-form-urlencoded; charset=UTF-8', - SERVER_PROTOCOL => 'HTTP/1.1', - REQUEST_URI => '/upload', - GATEWAY_INTERFACE => 'CGI/1.1', - SERVER_ADDR => '127.0.0.1', - DOCUMENT_ROOT => '/tmp/SnLu1cQ3t2', - PATH_TRANSLATED => '/tmp/test.fcgi/upload', - HTTP_HOST => '127.0.0.1:13028' -}); -$req->parse('hello=world'); -ok $req->is_finished, 'request is finished'; -is $req->method, 'POST', 'right method'; -is $req->url->base->host, '127.0.0.1', 'right base host'; -is $req->url->base->port, 13028, 'right base port'; -is $req->url->path, '', 'no path'; -is $req->url->base->path, '/upload/', 'right base path'; -is $req->version, '1.1', 'right version'; -ok !$req->is_secure, 'not secure'; -is $req->body, 'hello=world', 'right content'; -is_deeply $req->param('hello'), 'world', 'right parameters'; -is $req->url->to_abs->to_string, 'http://127.0.0.1:13028/upload/', 'right absolute URL'; +subtest 'Parse Apache 2.2.14 CGI environment variables and body (root)' => sub { + my $req = Mojo::Message::Request->new; + $req->parse({ + SCRIPT_NAME => '/upload', + SERVER_NAME => '127.0.0.1', + SERVER_ADMIN => '[no address given]', + PATH_INFO => '/upload', + HTTP_CONNECTION => 'Keep-Alive', + REQUEST_METHOD => 'POST', + CONTENT_LENGTH => '11', + SCRIPT_FILENAME => '/tmp/SnLu1cQ3t2/test.fcgi', + SERVER_SOFTWARE => 'Apache/2.2.14 (Unix) mod_fastcgi/2.4.2', + QUERY_STRING => '', + REMOTE_PORT => '58232', + HTTP_USER_AGENT => 'Mojolicious (Perl)', + SERVER_PORT => '13028', + SERVER_SIGNATURE => '', + REMOTE_ADDR => '127.0.0.1', + CONTENT_TYPE => 'application/x-www-form-urlencoded; charset=UTF-8', + SERVER_PROTOCOL => 'HTTP/1.1', + REQUEST_URI => '/upload', + GATEWAY_INTERFACE => 'CGI/1.1', + SERVER_ADDR => '127.0.0.1', + DOCUMENT_ROOT => '/tmp/SnLu1cQ3t2', + PATH_TRANSLATED => '/tmp/test.fcgi/upload', + HTTP_HOST => '127.0.0.1:13028' + }); + $req->parse('hello=world'); + ok $req->is_finished, 'request is finished'; + is $req->method, 'POST', 'right method'; + is $req->url->base->host, '127.0.0.1', 'right base host'; + is $req->url->base->port, 13028, 'right base port'; + is $req->url->path, '', 'no path'; + is $req->url->base->path, '/upload/', 'right base path'; + is $req->version, '1.1', 'right version'; + ok !$req->is_secure, 'not secure'; + is $req->body, 'hello=world', 'right content'; + is_deeply $req->param('hello'), 'world', 'right parameters'; + is $req->url->to_abs->to_string, 'http://127.0.0.1:13028/upload/', 'right absolute URL'; +}; -# Parse Apache 2.2.11 CGI environment variables and body (HTTPS=ON) -$req = Mojo::Message::Request->new; -$req->parse({ - CONTENT_LENGTH => 11, - CONTENT_TYPE => 'application/x-www-form-urlencoded', - PATH_INFO => '/foo/bar', - QUERY_STRING => '', - REQUEST_METHOD => 'GET', - SCRIPT_NAME => '/test/index.cgi', - HTTP_HOST => 'localhost', - HTTPS => 'ON', - SERVER_PROTOCOL => 'HTTP/1.0' -}); -$req->parse('hello=world'); -ok $req->is_finished, 'request is finished'; -is $req->method, 'GET', 'right method'; -is $req->url->base->host, 'localhost', 'right base host'; -is $req->url->path, 'foo/bar', 'right path'; -is $req->url->base->path, '/test/index.cgi/', 'right base path'; -is $req->version, '1.0', 'right version'; -ok $req->is_secure, 'is secure'; -is $req->body, 'hello=world', 'right content'; -is_deeply $req->param('hello'), 'world', 'right parameters'; -is $req->url->to_abs->to_string, 'https://localhost/test/index.cgi/foo/bar', 'right absolute URL'; +subtest 'Parse Apache 2.2.11 CGI environment variables and body (HTTPS=ON)' => sub { + my $req = Mojo::Message::Request->new; + $req->parse({ + CONTENT_LENGTH => 11, + CONTENT_TYPE => 'application/x-www-form-urlencoded', + PATH_INFO => '/foo/bar', + QUERY_STRING => '', + REQUEST_METHOD => 'GET', + SCRIPT_NAME => '/test/index.cgi', + HTTP_HOST => 'localhost', + HTTPS => 'ON', + SERVER_PROTOCOL => 'HTTP/1.0' + }); + $req->parse('hello=world'); + ok $req->is_finished, 'request is finished'; + is $req->method, 'GET', 'right method'; + is $req->url->base->host, 'localhost', 'right base host'; + is $req->url->path, 'foo/bar', 'right path'; + is $req->url->base->path, '/test/index.cgi/', 'right base path'; + is $req->version, '1.0', 'right version'; + ok $req->is_secure, 'is secure'; + is $req->body, 'hello=world', 'right content'; + is_deeply $req->param('hello'), 'world', 'right parameters'; + is $req->url->to_abs->to_string, 'https://localhost/test/index.cgi/foo/bar', 'right absolute URL'; +}; -# Parse Apache 2.2.11 CGI environment variables and body (trailing slash) -$req = Mojo::Message::Request->new; -$req->parse({ - CONTENT_LENGTH => 11, - CONTENT_TYPE => 'application/x-www-form-urlencoded', - PATH_INFO => '/foo/bar/', - QUERY_STRING => '', - REQUEST_METHOD => 'GET', - SCRIPT_NAME => '/test/index.cgi', - HTTP_HOST => 'localhost', - SERVER_PROTOCOL => 'HTTP/1.0' -}); -$req->parse('hello=world'); -ok $req->is_finished, 'request is finished'; -is $req->method, 'GET', 'right method'; -is $req->url->base->host, 'localhost', 'right base host'; -is $req->url->path, 'foo/bar/', 'right path'; -is $req->url->base->path, '/test/index.cgi/', 'right base path'; -is $req->version, '1.0', 'right version'; -is $req->body, 'hello=world', 'right content'; -is_deeply $req->param('hello'), 'world', 'right parameters'; -is $req->url->to_abs->to_string, 'http://localhost/test/index.cgi/foo/bar/', 'right absolute URL'; +subtest 'Parse Apache 2.2.11 CGI environment variables and body (trailing slash)' => sub { + my $req = Mojo::Message::Request->new; + $req->parse({ + CONTENT_LENGTH => 11, + CONTENT_TYPE => 'application/x-www-form-urlencoded', + PATH_INFO => '/foo/bar/', + QUERY_STRING => '', + REQUEST_METHOD => 'GET', + SCRIPT_NAME => '/test/index.cgi', + HTTP_HOST => 'localhost', + SERVER_PROTOCOL => 'HTTP/1.0' + }); + $req->parse('hello=world'); + ok $req->is_finished, 'request is finished'; + is $req->method, 'GET', 'right method'; + is $req->url->base->host, 'localhost', 'right base host'; + is $req->url->path, 'foo/bar/', 'right path'; + is $req->url->base->path, '/test/index.cgi/', 'right base path'; + is $req->version, '1.0', 'right version'; + is $req->body, 'hello=world', 'right content'; + is_deeply $req->param('hello'), 'world', 'right parameters'; + is $req->url->to_abs->to_string, 'http://localhost/test/index.cgi/foo/bar/', 'right absolute URL'; +}; -# Parse Apache 2.2.11 CGI environment variables and body (no SCRIPT_NAME) -$req = Mojo::Message::Request->new; -$req->parse({ - CONTENT_LENGTH => 11, - CONTENT_TYPE => 'application/x-www-form-urlencoded', - PATH_INFO => '/foo/bar', - QUERY_STRING => '', - REQUEST_METHOD => 'GET', - HTTP_HOST => 'localhost', - SERVER_PROTOCOL => 'HTTP/1.0' -}); -$req->parse('hello=world'); -ok $req->is_finished, 'request is finished'; -is $req->method, 'GET', 'right method'; -is $req->url->base->host, 'localhost', 'right base host'; -is $req->url->path, '/foo/bar', 'right path'; -is $req->url->base->path, '', 'no base path'; -is $req->version, '1.0', 'right version'; -is $req->body, 'hello=world', 'right content'; -is_deeply $req->param('hello'), 'world', 'right parameters'; -is $req->url->to_abs->to_string, 'http://localhost/foo/bar', 'right absolute URL'; +subtest 'Parse Apache 2.2.11 CGI environment variables and body (no SCRIPT_NAME)' => sub { + my $req = Mojo::Message::Request->new; + $req->parse({ + CONTENT_LENGTH => 11, + CONTENT_TYPE => 'application/x-www-form-urlencoded', + PATH_INFO => '/foo/bar', + QUERY_STRING => '', + REQUEST_METHOD => 'GET', + HTTP_HOST => 'localhost', + SERVER_PROTOCOL => 'HTTP/1.0' + }); + $req->parse('hello=world'); + ok $req->is_finished, 'request is finished'; + is $req->method, 'GET', 'right method'; + is $req->url->base->host, 'localhost', 'right base host'; + is $req->url->path, '/foo/bar', 'right path'; + is $req->url->base->path, '', 'no base path'; + is $req->version, '1.0', 'right version'; + is $req->body, 'hello=world', 'right content'; + is_deeply $req->param('hello'), 'world', 'right parameters'; + is $req->url->to_abs->to_string, 'http://localhost/foo/bar', 'right absolute URL'; +}; -# Parse Apache 2.2.11 CGI environment variables and body (no PATH_INFO) -$req = Mojo::Message::Request->new; -$req->parse({ - CONTENT_LENGTH => 11, - CONTENT_TYPE => 'application/x-www-form-urlencoded', - QUERY_STRING => '', - REQUEST_METHOD => 'GET', - SCRIPT_NAME => '/test/index.cgi', - HTTP_HOST => 'localhost', - SERVER_PROTOCOL => 'HTTP/1.0' -}); -$req->parse('hello=world'); -ok $req->is_finished, 'request is finished'; -is $req->method, 'GET', 'right method'; -is $req->url->base->host, 'localhost', 'right base host'; -is $req->url->path, '', 'no path'; -is $req->url->base->path, '/test/index.cgi/', 'right base path'; -is $req->version, '1.0', 'right version'; -is $req->body, 'hello=world', 'right content'; -is_deeply $req->param('hello'), 'world', 'right parameters'; -is $req->url->to_abs->to_string, 'http://localhost/test/index.cgi/', 'right absolute URL'; +subtest 'Parse Apache 2.2.11 CGI environment variables and body (no PATH_INFO)' => sub { + my $req = Mojo::Message::Request->new; + $req->parse({ + CONTENT_LENGTH => 11, + CONTENT_TYPE => 'application/x-www-form-urlencoded', + QUERY_STRING => '', + REQUEST_METHOD => 'GET', + SCRIPT_NAME => '/test/index.cgi', + HTTP_HOST => 'localhost', + SERVER_PROTOCOL => 'HTTP/1.0' + }); + $req->parse('hello=world'); + ok $req->is_finished, 'request is finished'; + is $req->method, 'GET', 'right method'; + is $req->url->base->host, 'localhost', 'right base host'; + is $req->url->path, '', 'no path'; + is $req->url->base->path, '/test/index.cgi/', 'right base path'; + is $req->version, '1.0', 'right version'; + is $req->body, 'hello=world', 'right content'; + is_deeply $req->param('hello'), 'world', 'right parameters'; + is $req->url->to_abs->to_string, 'http://localhost/test/index.cgi/', 'right absolute URL'; +}; -# Parse Apache 2.2.9 CGI environment variables (root without PATH_INFO) -$req = Mojo::Message::Request->new; -$req->parse({ - SCRIPT_NAME => '/cgi-bin/myapp/myapp.pl', - HTTP_CONNECTION => 'keep-alive', - HTTP_HOST => 'getmyapp.org', - REQUEST_METHOD => 'GET', - QUERY_STRING => '', - REQUEST_URI => '/cgi-bin/myapp/myapp.pl', - SERVER_PROTOCOL => 'HTTP/1.1', -}); -ok $req->is_finished, 'request is finished'; -is $req->method, 'GET', 'right method'; -is $req->url->base->host, 'getmyapp.org', 'right base host'; -is $req->url->path, '', 'no path'; -is $req->url->base->path, '/cgi-bin/myapp/myapp.pl/', 'right base path'; -is $req->version, '1.1', 'right version'; -is $req->url->to_abs->to_string, 'http://getmyapp.org/cgi-bin/myapp/myapp.pl/', 'right absolute URL'; +subtest 'Parse Apache 2.2.9 CGI environment variables (root without PATH_INFO)' => sub { + my $req = Mojo::Message::Request->new; + $req->parse({ + SCRIPT_NAME => '/cgi-bin/myapp/myapp.pl', + HTTP_CONNECTION => 'keep-alive', + HTTP_HOST => 'getmyapp.org', + REQUEST_METHOD => 'GET', + QUERY_STRING => '', + REQUEST_URI => '/cgi-bin/myapp/myapp.pl', + SERVER_PROTOCOL => 'HTTP/1.1', + }); + ok $req->is_finished, 'request is finished'; + is $req->method, 'GET', 'right method'; + is $req->url->base->host, 'getmyapp.org', 'right base host'; + is $req->url->path, '', 'no path'; + is $req->url->base->path, '/cgi-bin/myapp/myapp.pl/', 'right base path'; + is $req->version, '1.1', 'right version'; + is $req->url->to_abs->to_string, 'http://getmyapp.org/cgi-bin/myapp/myapp.pl/', 'right absolute URL'; +}; -# Parse Apache mod_fastcgi CGI environment variables (multipart file upload) -$req = Mojo::Message::Request->new; -is $req->content->progress, 0, 'right progress'; -$req->parse({ - SCRIPT_NAME => '', - SERVER_NAME => '127.0.0.1', - SERVER_ADMIN => '[no address given]', - PATH_INFO => '/upload', - HTTP_CONNECTION => 'Keep-Alive', - REQUEST_METHOD => 'POST', - CONTENT_LENGTH => '139', - SCRIPT_FILENAME => '/tmp/SnLu1cQ3t2/test.fcgi', - SERVER_SOFTWARE => 'Apache/2.2.14 (Unix) mod_fastcgi/2.4.2', - QUERY_STRING => '', - REMOTE_PORT => '58232', - HTTP_USER_AGENT => 'Mojolicious (Perl)', - SERVER_PORT => '13028', - SERVER_SIGNATURE => '', - REMOTE_ADDR => '127.0.0.1', - CONTENT_TYPE => 'multipart/form-data; boundary=8jXGX', - SERVER_PROTOCOL => 'HTTP/1.1', - PATH => '/usr/local/bin:/usr/local/sbin:/usr/bin:/bin:/usr/sbin:/sbin', - REQUEST_URI => '/upload', - GATEWAY_INTERFACE => 'CGI/1.1', - SERVER_ADDR => '127.0.0.1', - DOCUMENT_ROOT => '/tmp/SnLu1cQ3t2', - PATH_TRANSLATED => '/tmp/test.fcgi/upload', - HTTP_HOST => '127.0.0.1:13028' -}); -is $req->content->progress, 0, 'right progress'; -$req->parse("--8jXGX\x0d\x0a"); -is $req->content->progress, 9, 'right progress'; -$req->parse("Content-Disposition: form-data; name=\"file\"; filename=\"file.txt\"" - . "\x0d\x0aContent-Type: application/octet-stream\x0d\x0a\x0d\x0a"); -is $req->content->progress, 117, 'right progress'; -$req->parse('11023456789'); -is $req->content->progress, 128, 'right progress'; -$req->parse("\x0d\x0a--8jXGX--"); -is $req->content->progress, 139, 'right progress'; -ok $req->is_finished, 'request is finished'; -ok $req->content->is_multipart, 'multipart content'; -is $req->method, 'POST', 'right method'; -is $req->url->base->host, '127.0.0.1', 'right base host'; -is $req->url->path, '/upload', 'right path'; -is $req->url->base->path, '', 'no base path'; -is $req->version, '1.1', 'right version'; -is $req->url->to_abs->to_string, 'http://127.0.0.1:13028/upload', 'right absolute URL'; -my $file = $req->upload('file'); -is $file->filename, 'file.txt', 'right filename'; -is $file->slurp, '11023456789', 'right uploaded content'; +subtest 'Parse Apache mod_fastcgi CGI environment variables (multipart file upload)' => sub { + my $req = Mojo::Message::Request->new; + is $req->content->progress, 0, 'right progress'; + $req->parse({ + SCRIPT_NAME => '', + SERVER_NAME => '127.0.0.1', + SERVER_ADMIN => '[no address given]', + PATH_INFO => '/upload', + HTTP_CONNECTION => 'Keep-Alive', + REQUEST_METHOD => 'POST', + CONTENT_LENGTH => '139', + SCRIPT_FILENAME => '/tmp/SnLu1cQ3t2/test.fcgi', + SERVER_SOFTWARE => 'Apache/2.2.14 (Unix) mod_fastcgi/2.4.2', + QUERY_STRING => '', + REMOTE_PORT => '58232', + HTTP_USER_AGENT => 'Mojolicious (Perl)', + SERVER_PORT => '13028', + SERVER_SIGNATURE => '', + REMOTE_ADDR => '127.0.0.1', + CONTENT_TYPE => 'multipart/form-data; boundary=8jXGX', + SERVER_PROTOCOL => 'HTTP/1.1', + PATH => '/usr/local/bin:/usr/local/sbin:/usr/bin:/bin:/usr/sbin:/sbin', + REQUEST_URI => '/upload', + GATEWAY_INTERFACE => 'CGI/1.1', + SERVER_ADDR => '127.0.0.1', + DOCUMENT_ROOT => '/tmp/SnLu1cQ3t2', + PATH_TRANSLATED => '/tmp/test.fcgi/upload', + HTTP_HOST => '127.0.0.1:13028' + }); + is $req->content->progress, 0, 'right progress'; + $req->parse("--8jXGX\x0d\x0a"); + is $req->content->progress, 9, 'right progress'; + $req->parse("Content-Disposition: form-data; name=\"file\"; filename=\"file.txt\"" + . "\x0d\x0aContent-Type: application/octet-stream\x0d\x0a\x0d\x0a"); + is $req->content->progress, 117, 'right progress'; + $req->parse('11023456789'); + is $req->content->progress, 128, 'right progress'; + $req->parse("\x0d\x0a--8jXGX--"); + is $req->content->progress, 139, 'right progress'; + ok $req->is_finished, 'request is finished'; + ok $req->content->is_multipart, 'multipart content'; + is $req->method, 'POST', 'right method'; + is $req->url->base->host, '127.0.0.1', 'right base host'; + is $req->url->path, '/upload', 'right path'; + is $req->url->base->path, '', 'no base path'; + is $req->version, '1.1', 'right version'; + is $req->url->to_abs->to_string, 'http://127.0.0.1:13028/upload', 'right absolute URL'; + my $file = $req->upload('file'); + is $file->filename, 'file.txt', 'right filename'; + is $file->slurp, '11023456789', 'right uploaded content'; +}; -# Parse IIS 7.5 like CGI environment (HTTPS=off) -$req = Mojo::Message::Request->new; -$req->parse({ - CONTENT_LENGTH => 0, - PATH_INFO => '/index.pl/', - SERVER_SOFTWARE => 'Microsoft-IIS/7.5', - QUERY_STRING => '', - REQUEST_METHOD => 'GET', - SCRIPT_NAME => '/index.pl', - HTTP_HOST => 'test', - HTTPS => 'off', - SERVER_PROTOCOL => 'HTTP/1.1' -}); -ok $req->is_finished, 'request is finished'; -is $req->method, 'GET', 'right method'; -is $req->url->path, '', 'right URL'; -is $req->url->base->protocol, 'http', 'right base protocol'; -is $req->url->base->path, '/index.pl/', 'right base path'; -is $req->url->base->host, 'test', 'right base host'; -ok !$req->url->query->to_string, 'no query'; -is $req->version, '1.1', 'right version'; -ok !$req->is_secure, 'not secure'; +subtest 'Parse IIS 7.5 like CGI environment (HTTPS=off)' => sub { + my $req = Mojo::Message::Request->new; + $req->parse({ + CONTENT_LENGTH => 0, + PATH_INFO => '/index.pl/', + SERVER_SOFTWARE => 'Microsoft-IIS/7.5', + QUERY_STRING => '', + REQUEST_METHOD => 'GET', + SCRIPT_NAME => '/index.pl', + HTTP_HOST => 'test', + HTTPS => 'off', + SERVER_PROTOCOL => 'HTTP/1.1' + }); + ok $req->is_finished, 'request is finished'; + is $req->method, 'GET', 'right method'; + is $req->url->path, '', 'right URL'; + is $req->url->base->protocol, 'http', 'right base protocol'; + is $req->url->base->path, '/index.pl/', 'right base path'; + is $req->url->base->host, 'test', 'right base host'; + ok !$req->url->query->to_string, 'no query'; + is $req->version, '1.1', 'right version'; + ok !$req->is_secure, 'not secure'; +}; done_testing();