#include "common.hh" #include "http_protocol.hh" #include "str_buffer.hh" #include 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(); 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(); 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(); 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(); 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(); 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(); 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(); 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(); 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()); }