diff options
Diffstat (limited to 'test/test_http_protocol.cc')
| -rw-r--r-- | test/test_http_protocol.cc | 470 |
1 files changed, 470 insertions, 0 deletions
diff --git a/test/test_http_protocol.cc b/test/test_http_protocol.cc new file mode 100644 index 0000000..9fe1c45 --- /dev/null +++ b/test/test_http_protocol.cc @@ -0,0 +1,470 @@ +#include "common.hh" + +#include "http_protocol.hh" +#include "str_buffer.hh" + +#include <gtest/gtest.h> + +TEST(http_protocol, standard_message) { + EXPECT_EQ("", http_standard_message(199)); + EXPECT_EQ("Continue", http_standard_message(100)); + EXPECT_EQ("Switching Protocols", http_standard_message(101)); + EXPECT_EQ("OK", http_standard_message(200)); + EXPECT_EQ("Created", http_standard_message(201)); + EXPECT_EQ("Accepted", http_standard_message(202)); + EXPECT_EQ("Non-Authorative Information", http_standard_message(203)); + EXPECT_EQ("No Content", http_standard_message(204)); + EXPECT_EQ("Reset Content", http_standard_message(205)); + EXPECT_EQ("Partial Content", http_standard_message(206)); + EXPECT_EQ("Multiple Choices", http_standard_message(300)); + EXPECT_EQ("Moved Permanently", http_standard_message(301)); + EXPECT_EQ("Found", http_standard_message(302)); + EXPECT_EQ("See Other", http_standard_message(303)); + EXPECT_EQ("Not Modified", http_standard_message(304)); + EXPECT_EQ("Use Proxy", http_standard_message(305)); + EXPECT_EQ("Temporary Redirect", http_standard_message(307)); + EXPECT_EQ("Bad Request", http_standard_message(400)); + EXPECT_EQ("Unauthorized", http_standard_message(401)); + EXPECT_EQ("Payment Required", http_standard_message(402)); + EXPECT_EQ("Forbidden", http_standard_message(403)); + EXPECT_EQ("Not Found", http_standard_message(404)); + EXPECT_EQ("Method Not Allowed", http_standard_message(405)); + EXPECT_EQ("Not Acceptable", http_standard_message(406)); + EXPECT_EQ("Proxy Authentication Required", http_standard_message(407)); + EXPECT_EQ("Request Timeout", http_standard_message(408)); + EXPECT_EQ("Conflict", http_standard_message(409)); + EXPECT_EQ("Gone", http_standard_message(410)); + EXPECT_EQ("Length Required", http_standard_message(411)); + EXPECT_EQ("Precondition Failed", http_standard_message(412)); + EXPECT_EQ("Request Entity Too Large", http_standard_message(413)); + EXPECT_EQ("Request-URI Too Long", http_standard_message(414)); + EXPECT_EQ("Unsupported Media Type", http_standard_message(415)); + EXPECT_EQ("Requested Range Not Satisfiable", http_standard_message(416)); + EXPECT_EQ("Expectation Failed", http_standard_message(417)); + EXPECT_EQ("Internal Server Error", http_standard_message(500)); + EXPECT_EQ("Not Implemented", http_standard_message(501)); + EXPECT_EQ("Bad Gateway", http_standard_message(502)); + EXPECT_EQ("Service Unavailable", http_standard_message(503)); + EXPECT_EQ("Gateway Timeout", http_standard_message(504)); + EXPECT_EQ("HTTP Version Not Supported", http_standard_message(505)); +} + +TEST(http_protocol, date) { + EXPECT_EQ("Thu, 01 Jan 1970 00:00:00 GMT", http_date(0)); +} + +TEST(http_protocol, parse_request_empty) { + auto buf = make_strbuffer(std::string()); + EXPECT_EQ(nullptr, HttpRequest::parse(buf.get())); +} + +TEST(http_protocol, parse_request_partial_multiline_header) { + auto str = std::string_view( + "GET / HTTP/1.1\r\n" + " example.org\r\n" + "\r\n"); + auto buf = make_strbuffer(str); + auto req = HttpRequest::parse(buf.get()); + ASSERT_TRUE(req); + EXPECT_FALSE(req->good()); +} + +TEST(http_protocol, parse_request_simple) { + auto str = std::string_view( + "GET / HTTP/1.1\r\n" + "Host: example.org\r\n" + "\r\n"); + // Verify that partial requests fail + for (size_t i = 1; i < str.size() - 1; ++i) { + auto buf = make_strbuffer(str.substr(0, i)); + EXPECT_EQ(nullptr, HttpRequest::parse(buf.get())); + } + auto buf = make_strbuffer(str); + auto req = HttpRequest::parse(buf.get()); + ASSERT_TRUE(req); + EXPECT_TRUE(req->good()); + EXPECT_EQ("GET", req->method()); + EXPECT_EQ("/", req->url()); + EXPECT_EQ("HTTP", req->proto()); + EXPECT_EQ(1, req->proto_version().major); + EXPECT_EQ(1, req->proto_version().minor); + EXPECT_EQ("example.org", req->first_header("host")); + EXPECT_EQ(str.size(), req->size()); + EXPECT_TRUE(buf->empty()); +} + +TEST(http_protocol, parse_request_multiple) { + auto post = std::string_view( + "POST /foo HTTP/1.1\r\n" + "Host: example.org\r\n" + "Content-Length: 3\r\n" + "Content-Type: text/plain; charset=utf-8\r\n" + "\r\n" + "bar"); + auto get = std::string_view( + "GET /bar HTTP/1.0\r\n" + "\r\n"); + auto buf = make_strbuffer(std::string(post) + std::string(get)); + auto req = HttpRequest::parse(buf.get()); + ASSERT_TRUE(req); + EXPECT_EQ("POST", req->method()); + EXPECT_EQ("/foo", req->url()); + EXPECT_EQ("HTTP", req->proto()); + EXPECT_EQ(1, req->proto_version().major); + EXPECT_EQ(1, req->proto_version().minor); + EXPECT_EQ("example.org", req->first_header("host")); + EXPECT_EQ("3", req->first_header("content-length")); + EXPECT_EQ("text/plain; charset=utf-8", req->first_header("content-type")); + auto token_it = req->header_tokens("content-type"); + EXPECT_TRUE(token_it->valid()); + if (token_it->valid()) { + EXPECT_EQ("text", token_it->token()); + token_it->next(); + EXPECT_FALSE(token_it->valid()); + } + EXPECT_EQ(post.size() - 3, req->size()); + char tmp[3]; + RoBuffer::read(buf.get(), tmp, 3); + EXPECT_EQ("bar", std::string_view(tmp, 3)); + req = HttpRequest::parse(buf.get()); + ASSERT_TRUE(req); + EXPECT_TRUE(req->good()); + EXPECT_EQ("GET", req->method()); + EXPECT_EQ("/bar", req->url()); + EXPECT_EQ("HTTP", req->proto()); + EXPECT_EQ(1, req->proto_version().major); + EXPECT_EQ(0, req->proto_version().minor); + EXPECT_EQ("", req->first_header("host")); + EXPECT_EQ(get.size(), req->size()); + EXPECT_TRUE(buf->empty()); +} + +TEST(http_protocol, parse_request_multiline_header) { + auto str = std::string_view( + "GET / HTTP/1.1\r\n" + "Accept: image/jpeg, image/png\r\n" + " image/gif\r\n" + "Accept: image/x-webp\r\n" + "\r\n"); + auto buf = make_strbuffer(str); + auto req = HttpRequest::parse(buf.get()); + ASSERT_TRUE(req); + EXPECT_TRUE(req->good()); + EXPECT_EQ("image/jpeg, image/png,image/gif", + req->first_header("accept")); + std::string values; + for (auto it = req->header("accept"); it->valid(); it->next()) { + values.push_back('|'); + values.append(it->value()); + } + EXPECT_EQ("|image/jpeg, image/png,image/gif|image/x-webp", values); + EXPECT_EQ(str.size(), req->size()); + EXPECT_TRUE(buf->empty()); +} + +TEST(http_protocol, parse_request_multiline_set_cookie_header) { + // Set-Cookie can't be joined, so the below really shouldn't + // ever be sent. Could treat it as invalid, could pretend + // Set-Cookie was set three times, or, as current implmenetation does, + // ignore the multiline line. + auto str = std::string_view( + "GET / HTTP/1.1\r\n" + "Set-Cookie: a=1\r\n" + " b=2\r\n" + "Set-Cookie: c=3\r\n" + "\r\n"); + auto buf = make_strbuffer(str); + auto req = HttpRequest::parse(buf.get()); + ASSERT_TRUE(req); + EXPECT_TRUE(req->good()); + EXPECT_EQ("a=1", req->first_header("set-cookie")); + std::string values; + for (auto it = req->header("set-cookie"); it->valid(); it->next()) { + values.push_back('|'); + values.append(it->value()); + } + EXPECT_EQ("|a=1|c=3", values); + EXPECT_EQ(str.size(), req->size()); + EXPECT_TRUE(buf->empty()); +} + +TEST(http_protocol, parse_request_tokens) { + // Note that this mainly shows that you shouldn't use header_tokens() for + // if-none-match... but it's a header that contains a lot of quoted strings. + auto str = std::string_view( + "GET / HTTP/1.1\r\n" + "If-None-Match: *, W/\"foo\", \"bar\"\r\n" + "\r\n"); + auto buf = make_strbuffer(str); + auto req = HttpRequest::parse(buf.get()); + ASSERT_TRUE(req); + EXPECT_TRUE(req->good()); + EXPECT_EQ("*, W/\"foo\", \"bar\"", req->first_header("if-none-match")); + std::string tokens; + for (auto it = req->header_tokens("if-none-match"); it->valid(); it->next()) { + tokens.push_back('|'); + tokens.append(it->token()); + } + EXPECT_EQ("|*|W", tokens); + EXPECT_EQ(str.size(), req->size()); + EXPECT_TRUE(buf->empty()); +} + +TEST(http_protocol, request_builder_simple) { + auto str = std::make_shared<std::string>(); + auto buf = make_strbuffer(str); + auto builder = HttpRequestBuilder::create("GET", "/", "HTTP", Version(1, 1)); + builder->add_header("Host", "example.org"); + EXPECT_TRUE(builder->build(buf.get())); + EXPECT_EQ("GET / HTTP/1.1\r\n" + "Host: example.org\r\n" + "\r\n", *str); + EXPECT_EQ(str->size(), builder->size()); +} + +TEST(http_protocol, request_builder_post) { + auto str = std::make_shared<std::string>(); + auto buf = make_strbuffer(str); + auto builder = HttpRequestBuilder::create("POST", "/foo", "HTTP", + Version(1, 1)); + builder->add_header("Host", "example.org"); + builder->add_header("Content-Length", "3"); + builder->add_header("Content-Type", "text/plain; charset=utf-8"); + EXPECT_TRUE(builder->build(buf.get())); + EXPECT_EQ("POST /foo HTTP/1.1\r\n" + "Host: example.org\r\n" + "Content-Length: 3\r\n" + "Content-Type: text/plain; charset=utf-8\r\n" + "\r\n", *str); + EXPECT_EQ(str->size(), builder->size()); +} + +TEST(http_protocol, request_builder_10) { + auto str = std::make_shared<std::string>(); + auto buf = make_strbuffer(str); + auto builder = HttpRequestBuilder::create("GET", "/bar", "HTTP", + Version(1, 0)); + EXPECT_TRUE(builder->build(buf.get())); + EXPECT_EQ("GET /bar HTTP/1.0\r\n" + "\r\n", *str); + EXPECT_EQ(str->size(), builder->size()); +} + +TEST(http_protocol, request_builder_multiline_header) { + auto str = std::make_shared<std::string>(); + auto buf = make_strbuffer(str); + auto builder = HttpRequestBuilder::create("GET", "/", "HTTP", Version(1, 1)); + builder->add_header("Accept", "image/jpeg, image/png"); + builder->add_header("", "image/gif"); + builder->add_header("Accept", "image/x-webp"); + EXPECT_TRUE(builder->build(buf.get())); + EXPECT_EQ("GET / HTTP/1.1\r\n" + "Accept: image/jpeg, image/png\r\n" + " image/gif\r\n" + "Accept: image/x-webp\r\n" + "\r\n", *str); + EXPECT_EQ(str->size(), builder->size()); +} + +TEST(http_protocol, parse_response_empty) { + auto buf = make_strbuffer(std::string()); + EXPECT_EQ(nullptr, HttpResponse::parse(buf.get())); +} + +TEST(http_protocol, parse_response_simple) { + auto str = std::string_view( + "HTTP/1.1 200 OK\r\n" + "Content-Length: 3\r\n" + "Content-Type: text/plain; charset=utf-8\r\n" + "Connection: close\r\n" + "\r\n" + "foo"); + // Verify partial parses fail + for (size_t i = 1; i < str.size() - 4; ++i) { + auto buf = make_strbuffer(str.substr(0, i)); + EXPECT_EQ(nullptr, HttpResponse::parse(buf.get())); + } + auto buf = make_strbuffer(str); + auto resp = HttpResponse::parse(buf.get()); + ASSERT_TRUE(resp); + EXPECT_TRUE(resp->good()); + EXPECT_EQ("HTTP", resp->proto()); + EXPECT_EQ(1, resp->proto_version().major); + EXPECT_EQ(1, resp->proto_version().minor); + EXPECT_EQ(200, resp->status_code()); + EXPECT_EQ("OK", resp->status_message()); + EXPECT_EQ("3", resp->first_header("content-length")); + EXPECT_EQ("text/plain; charset=utf-8", resp->first_header("content-type")); + EXPECT_EQ("close", resp->first_header("connection")); + EXPECT_EQ(str.size() - 3, resp->size()); + { + char tmp[3]; + RoBuffer::read(buf.get(), tmp, 3); + EXPECT_EQ("foo", std::string_view(tmp, 3)); + } + EXPECT_TRUE(buf->empty()); +} + +TEST(http_protocol, parse_response_multiple) { + auto redirect = std::string_view( + "HTTP/1.1 302 Redirecting for fun\r\n" + "Location: /foo\r\n" + "\r\n"); + auto result = std::string_view( + "HTTP/1.0 204 No Content\r\n" + "Connection: close\r\n" + "\r\n"); + auto buf = make_strbuffer(std::string(redirect) + std::string(result)); + auto resp = HttpResponse::parse(buf.get()); + ASSERT_TRUE(resp); + EXPECT_TRUE(resp->good()); + EXPECT_EQ("HTTP", resp->proto()); + EXPECT_EQ(1, resp->proto_version().major); + EXPECT_EQ(1, resp->proto_version().minor); + EXPECT_EQ(302, resp->status_code()); + EXPECT_EQ("Redirecting for fun", resp->status_message()); + EXPECT_EQ("/foo", resp->first_header("location")); + EXPECT_EQ(redirect.size(), resp->size()); + resp = HttpResponse::parse(buf.get()); + ASSERT_TRUE(resp); + EXPECT_EQ("HTTP", resp->proto()); + EXPECT_EQ(1, resp->proto_version().major); + EXPECT_EQ(0, resp->proto_version().minor); + EXPECT_EQ(204, resp->status_code()); + EXPECT_EQ("No Content", resp->status_message()); + auto token_it = resp->header_tokens("connection"); + EXPECT_TRUE(token_it->valid()); + if (token_it->valid()) { + EXPECT_EQ("close", token_it->token()); + token_it->next(); + EXPECT_FALSE(token_it->valid()); + } + EXPECT_EQ(result.size(), resp->size()); + EXPECT_TRUE(buf->empty()); +} + +TEST(http_protocol, parse_response_simple_only_newline) { + auto str = std::string_view( + "HTTP/1.1 200 OK\n" + "Content-Length: 3\n" + "Content-Type: text/plain; charset=utf-8\n" + "Connection: close\n" + "\n" + "foo"); + for (size_t i = 1; i < str.size() - 4; ++i) { + auto buf = make_strbuffer(str.substr(0, i)); + EXPECT_EQ(nullptr, HttpResponse::parse(buf.get())); + } + auto buf = make_strbuffer(str); + auto resp = HttpResponse::parse(buf.get()); + ASSERT_TRUE(resp); + EXPECT_TRUE(resp->good()); + EXPECT_EQ("HTTP", resp->proto()); + EXPECT_EQ(1, resp->proto_version().major); + EXPECT_EQ(1, resp->proto_version().minor); + EXPECT_EQ(200, resp->status_code()); + EXPECT_EQ("OK", resp->status_message()); + EXPECT_EQ("3", resp->first_header("content-length")); + EXPECT_EQ("text/plain; charset=utf-8", resp->first_header("content-type")); + EXPECT_EQ("close", resp->first_header("connection")); + EXPECT_EQ(str.size() - 3, resp->size()); + { + char tmp[3]; + RoBuffer::read(buf.get(), tmp, 3); + EXPECT_EQ("foo", std::string_view(tmp, 3)); + } + EXPECT_TRUE(buf->empty()); +} + +TEST(http_protocol, parse_response_simple_only_linebreak) { + auto str = std::string_view( + "HTTP/1.1 200 OK\r" + "Content-Length: 3\r" + "Content-Type: text/plain; charset=utf-8\r" + "Connection: close\r" + "\r" + "foo"); + for (size_t i = 1; i < str.size() - 4; ++i) { + auto buf = make_strbuffer(str.substr(0, i)); + EXPECT_EQ(nullptr, HttpResponse::parse(buf.get())); + } + auto buf = make_strbuffer(str); + auto resp = HttpResponse::parse(buf.get()); + ASSERT_TRUE(resp); + EXPECT_TRUE(resp->good()); + EXPECT_EQ("HTTP", resp->proto()); + EXPECT_EQ(1, resp->proto_version().major); + EXPECT_EQ(1, resp->proto_version().minor); + EXPECT_EQ(200, resp->status_code()); + EXPECT_EQ("OK", resp->status_message()); + EXPECT_EQ("3", resp->first_header("content-length")); + EXPECT_EQ("text/plain; charset=utf-8", resp->first_header("content-type")); + EXPECT_EQ("close", resp->first_header("connection")); + EXPECT_EQ(str.size() - 3, resp->size()); + { + char tmp[3]; + RoBuffer::read(buf.get(), tmp, 3); + EXPECT_EQ("foo", std::string_view(tmp, 3)); + } + EXPECT_TRUE(buf->empty()); +} + +TEST(http_protocol, response_builder_simple) { + auto str = std::make_shared<std::string>(); + auto buf = make_strbuffer(str); + + auto builder = HttpResponseBuilder::create("HTTP", Version(1, 1), 200, "OK"); + builder->add_header("Content-Length", "3"); + builder->add_header("Content-Type", "text/plain; charset=utf-8"); + builder->add_header("Connection", "close"); + EXPECT_TRUE(builder->build(buf.get())); + EXPECT_EQ("HTTP/1.1 200 OK\r\n" + "Content-Length: 3\r\n" + "Content-Type: text/plain; charset=utf-8\r\n" + "Connection: close\r\n" + "\r\n", *str); + EXPECT_EQ(str->size(), builder->size()); +} + +TEST(http_protocol, response_builder_redirect) { + auto str = std::make_shared<std::string>(); + auto buf = make_strbuffer(str); + auto builder = HttpResponseBuilder::create("HTTP", Version(1, 1), + 302, "Redirecting for fun"); + builder->add_header("Location", "/foo"); + EXPECT_TRUE(builder->build(buf.get())); + EXPECT_EQ("HTTP/1.1 302 Redirecting for fun\r\n" + "Location: /foo\r\n" + "\r\n", *str); + EXPECT_EQ(str->size(), builder->size()); +} + +TEST(http_protocol, response_builder_no_content) { + auto str = std::make_shared<std::string>(); + auto buf = make_strbuffer(str); + auto builder = HttpResponseBuilder::create("HTTP", Version(1, 0), + 204, "No Content"); + builder->add_header("Connection", "close"); + EXPECT_TRUE(builder->build(buf.get())); + EXPECT_EQ("HTTP/1.0 204 No Content\r\n" + "Connection: close\r\n" + "\r\n", *str); + EXPECT_EQ(str->size(), builder->size()); +} + +TEST(http_protocol, response_cgi_builder_simple) { + auto str = std::make_shared<std::string>(); + auto buf = make_strbuffer(str); + + auto builder = CgiResponseBuilder::create(200); + builder->add_header("Content-Length", "3"); + builder->add_header("Content-Type", "text/plain; charset=utf-8"); + builder->add_header("Connection", "close"); + EXPECT_TRUE(builder->build(buf.get())); + EXPECT_EQ("Status: 200\r\n" + "Content-Length: 3\r\n" + "Content-Type: text/plain; charset=utf-8\r\n" + "Connection: close\r\n" + "\r\n", *str); + EXPECT_EQ(str->size(), builder->size()); +} |
