summaryrefslogtreecommitdiff
path: root/test/test_http_protocol.cc
diff options
context:
space:
mode:
Diffstat (limited to 'test/test_http_protocol.cc')
-rw-r--r--test/test_http_protocol.cc470
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());
+}