// -*- mode: c++; c-basic-offset: 2; -*- #include "common.hh" #include "test.hh" #include #include #include "http.hh" namespace { bool find_header(HeaderIterator* iter, std::string const& name, std::string* value) { for (; iter->valid(); iter->next()) { if (iter->name_equal(name)) { if (value) value->assign(iter->value()); return true; } } return false; } bool good_resp(std::string const& name, std::string const& in, std::string const& proto, uint16_t major, uint16_t minor, uint16_t status, std::string const& status_msg, char const* content, ...) { std::unique_ptr resp; size_t test = 0; while (test <= in.size()) { resp.reset(HttpResponse::parse(in.substr(0, test))); if (resp) break; ++test; } if (!resp || !resp->good()) { std::cerr << "good_resp:" << name << ": Invalid response" << std::endl; return false; } resp.reset(HttpResponse::parse(in)); if (!resp || !resp->good()) { std::cerr << "good_resp:" << name << ": Incomplete/invalid response" << std::endl; return false; } if (proto != resp->proto() || !resp->proto_equal(proto)) { std::cerr << "good_resp:" << name << ":protocol: Expected " << proto << " got " << resp->proto() << std::endl; return false; } if (major != resp->proto_version().major || minor != resp->proto_version().minor) { std::cerr << "good_resp:" << name << ":protocol_version: Expected " << major << "." << minor << " got " << resp->proto_version().major << "." << resp->proto_version().minor << std::endl; return false; } if (status != resp->status_code()) { std::cerr << "good_resp:" << name << ":status: Expected " << status << " got " << resp->status_code() << std::endl; return false; } if (status_msg != resp->status_message()) { std::cerr << "good_resp:" << name << ":status: Expected " << status_msg << " got " << resp->status_message() << std::endl; return false; } va_list headers; va_start(headers, content); while (true) { auto header = va_arg(headers, char const*); if (!header) break; auto value = va_arg(headers, char const*); auto iter = resp->header(); std::string tmp; if (find_header(iter.get(), header, &tmp)) { if (tmp.compare(value)) { std::cerr << "good_resp:" << name << ":header" << header << ": Expected " << value << " got " << tmp << std::endl; return false; } } else { std::cerr << "good_resp:" << name << ":header:" << header << ": Expected " << value << " got no value" << std::endl; return false; } iter = resp->header(header); if (!iter->valid()) { std::cerr << "good_resp:" << name << ":header2:" << header << ": Expected " << value << " got no value" << std::endl; return false; } if (!iter->name_equal(header)) { std::cerr << "good_resp:" << name << ":header2:" << header << ": Expected " << header << " got " << iter->name() << std::endl; return false; } if (iter->value().compare(value)) { std::cerr << "good_resp:" << name << ":header2:" << header << ": Expected " << value << " got " << iter->value() << std::endl; return false; } iter->next(); if (iter->valid()) { std::cerr << "good_resp:" << name << ":header2:" << header << ": Expected " << value << " got multiple values" << std::endl; return false; } } va_end(headers); if (resp->size() != test) { std::cerr << "good_resp:" << name << ":size: Expected " << test << " got " << resp->size() << std::endl; return false; } auto size = strlen(content); std::string rest = in.substr(resp->size()); if (rest.size() != size) { std::cerr << "good_resp:" << name << ":content: Expected " << size << " bytes got " << rest.size() << std::endl; return false; } if (memcmp(content, rest.data(), size)) { std::cerr << "good_resp:" << name << ":content: Expected " << content << " got " << rest << std::endl; return false; } return true; } bool good_req(std::string const& name, std::string const& in, std::string const& method, std::string const& url, std::string const& proto, uint16_t major, uint16_t minor, char const* content, ...) { std::unique_ptr req; size_t test = 0; while (test <= in.size()) { req.reset(HttpRequest::parse(in.substr(0, test))); if (req) break; ++test; } if (!req || !req->good()) { std::cerr << "good_req:" << name << ": Invalid request" << std::endl; return false; } req.reset(HttpRequest::parse(in)); if (!req || !req->good()) { std::cerr << "good_req:" << name << ": Incomplete/invalid request" << std::endl; return false; } if (method != req->method() || !req->method_equal(method)) { std::cerr << "good_req:" << name << ":protocol: Expected " << proto << " got " << req->proto() << std::endl; return false; } if (url != req->url()) { std::cerr << "good_req:" << name << ":url: Expected " << url << " got " << req->url() << std::endl; return false; } if (proto != req->proto() || !req->proto_equal(proto)) { std::cerr << "good_req:" << name << ":protocol: Expected '" << proto << "' got '" << req->proto() << "'" << std::endl; return false; } if (major != req->proto_version().major || minor != req->proto_version().minor) { std::cerr << "good_req:" << name << ":protocol_version: Expected " << major << "." << minor << " got " << req->proto_version().major << "." << req->proto_version().minor << std::endl; return false; } va_list headers; va_start(headers, content); while (true) { auto header = va_arg(headers, char const*); if (!header) break; auto value = va_arg(headers, char const*); auto iter = req->header(); std::string tmp; if (find_header(iter.get(), header, &tmp)) { if (tmp.compare(value)) { std::cerr << "good_req:" << name << ":header" << header << ": Expected " << value << " got " << tmp << std::endl; return false; } } else { std::cerr << "good_req:" << name << ":header:" << header << ": Expected " << value << " got no value" << std::endl; return false; } iter = req->header(header); if (!iter->valid()) { std::cerr << "good_req:" << name << ":header2:" << header << ": Expected " << value << " got no value" << std::endl; return false; } if (!iter->name_equal(header)) { std::cerr << "good_req:" << name << ":header2:" << header << ": Expected " << header << " got " << iter->name() << std::endl; return false; } if (iter->value().compare(value)) { std::cerr << "good_req:" << name << ":header2:" << header << ": Expected " << value << " got " << iter->value() << std::endl; return false; } iter->next(); if (iter->valid()) { std::cerr << "good_req:" << name << ":header2:" << header << ": Expected " << value << " got multiple values" << std::endl; return false; } } va_end(headers); if (req->size() != test) { std::cerr << "good_req:" << name << ":size: Expected " << test << " got " << req->size() << std::endl; return false; } auto size = strlen(content); std::string rest = in.substr(req->size()); if (rest.size() != size) { std::cerr << "good_req:" << name << ":content: Expected " << size << " bytes got " << rest.size() << std::endl; return false; } if (memcmp(content, rest.data(), size)) { std::cerr << "good_req:" << name << ":content: Expected " << content << " got " << rest << std::endl; return false; } return true; } bool bad_resp(std::string const& name, std::string const& in) { std::unique_ptr resp(HttpResponse::parse(in)); if (!resp || resp->good()) { std::cerr << "bad_resp:" << name << ": Expected invalid response" << std::endl; return false; } return true; } bool short_resp(std::string const& name, std::string const& in) { std::unique_ptr resp(HttpResponse::parse(in)); if (resp) { std::cerr << "short_resp:" << name << ": Expected incomplete response" << std::endl; return false; } return true; } bool req(std::string const& name, std::string const& out, std::string const& method, std::string const& url, std::string const& proto, uint16_t major, uint16_t minor, char const* content, ...) { Version ver; ver.major = major; ver.minor = minor; std::unique_ptr r( HttpRequestBuilder::create(method, url, proto, ver)); if (content) r->set_content(content); va_list headers; va_start(headers, content); while (true) { auto header = va_arg(headers, const char*); if (!header) break; auto value = va_arg(headers, const char*); r->add_header(header, value); } va_end(headers); auto tmp = r->build(); if (tmp != out) { std::cerr << "req:" << name << ": Expected:\n" << out << "Got:\n" << tmp << std::endl; return false; } return true; } bool resp(std::string const& name, std::string const& out, std::string const& proto, uint16_t major, uint16_t minor, uint16_t status_code, std::string const& status_message, char const* content, ...) { Version ver; ver.major = major; ver.minor = minor; std::unique_ptr r( HttpResponseBuilder::create(proto, ver, status_code, status_message)); if (content) r->set_content(content); va_list headers; va_start(headers, content); while (true) { auto header = va_arg(headers, const char*); if (!header) break; auto value = va_arg(headers, const char*); r->add_header(header, value); } va_end(headers); auto tmp = r->build(); if (tmp != out) { std::cerr << "resp:" << name << ": Expected:\n" << out << "Got:\n" << tmp << std::endl; return false; } return true; } } // namespace int main() { BEFORE; RUN(good_resp("index_200_html", "HTTP/1.1 200 OK\r\n" "Content-Type: text/html\r\n" "Accept-Ranges: bytes\r\n" "ETag: \"1908074049\"\r\n" "Last-Modified: Sat, 21 Apr 2012 09:42:46 GMT\r\n" "Content-Length: 54\r\n" "Date: Sat, 18 Feb 2017 14:30:13 GMT\r\n" "Server: lighttpd/1.4.33\r\n" "\r\n" "", "HTTP", 1, 1, 200, "OK", "", "content-type", "text/html", "accept-ranges", "bytes", "etag", "\"1908074049\"", "last-modified", "Sat, 21 Apr 2012 09:42:46 GMT", "content-length", "54", "date", "Sat, 18 Feb 2017 14:30:13 GMT", "Server", "lighttpd/1.4.33", nullptr)); RUN(good_resp("linux_newline", "HTTP/1.0 200 OK\n" "OS: Unix\n" "\n", "HTTP", 1, 0, 200, "OK", "", "os", "Unix", nullptr)); RUN(good_resp("mac_newline", "HTTP/1.0 200 OK\r" "OS: Unix\r" "\r", "HTTP", 1, 0, 200, "OK", "", "os", "Unix", nullptr)); RUN(good_resp("empty_status_msg", "HTTP/1.0 200 \r\n" "\r\n", "HTTP", 1, 0, 200, "", "", nullptr)); RUN(good_resp("empty_status_msg_without_separator", "HTTP/1.0 200\r\n" "\r\n", "HTTP", 1, 0, 200, "", "", nullptr)); RUN(good_resp("continue_header", "HTTP/1.0 200 OK\r\n" "X: foo\r\n" " bar\r\n" "Y: test\r\n" "\r\n", "HTTP", 1, 0, 200, "OK", "", "X", "foo,bar", "Y", "test", nullptr)); RUN(good_resp("header_missing_space", "HTTP/1.0 200 OK\r\n" "X:foo\r\n" "\r\n", "HTTP", 1, 0, 200, "OK", "", "X", "foo", nullptr)); RUN(good_req("minimal_request", "GET / HTTP/1.0\r\n" "\r\n", "GET", "/", "HTTP", 1, 0, "", nullptr)); RUN(short_resp("empty", "")); RUN(bad_resp("missing_version", "HTTP 200 OK\r\n\r\n")); RUN(bad_resp("missing_minor", "HTTP/1 200 OK\r\n\r\n")); RUN(bad_resp("missing_bad_major", "HTTP/X.1 200 OK\r\n\r\n")); RUN(bad_resp("missing_bad_minor", "HTTP/1.Y 200 OK\r\n\r\n")); RUN(bad_resp("missing_bad_status", "HTTP/1.0 000 OK\r\n\r\n")); RUN(short_resp("missing_end", "HTTP/1.0 200 OK\r\n")); RUN(req("basic", "GET / HTTP/1.1\r\n" "Host: example.org\r\n" "User-Agent: curl/7.52.1\r\n" "Accept: */*\r\n" "\r\n", "GET", "/", "HTTP", 1, 1, nullptr, "host", "example.org", "user-agent", "curl/7.52.1", "accept", "*/*", nullptr)); RUN(req("short", "GET / HTTP/1.0\r\n" "\r\n", "GET", "/", "HTTP", 1, 0, nullptr, nullptr)); RUN(req("post", "POST / HTTP/1.1\r\n" "Host: example.org\r\n" "Content-Length: 3\r\n" "\r\n" "foo", "POST", "/", "HTTP", 1, 1, "foo", "host", "example.org", nullptr)); RUN(req("post_with_set_content_length", "POST / HTTP/1.1\r\n" "Host: example.org\r\n" "Content-Length: 6\r\n" "\r\n" "foo", "POST", "/", "HTTP", 1, 1, "foo", "host", "example.org", "content-length", "6", nullptr)); RUN(req("proxy_get", "GET http://example.org/foo HTTP/1.1\r\n" "Host: example.org\r\n" "\r\n", "GET", "http://example.org/foo", "HTTP", 1, 1, nullptr, "host", "example.org", nullptr)); RUN(req("multiline_header", "GET / HTTP/1.1\r\n" "Host: example.org\r\n" "X: foo\r\n" " bar\r\n" "\r\n", "GET", "/", "HTTP", 1, 1, nullptr, "host", "example.org", "x", "foo", "", "bar", nullptr)); RUN(resp("minimal_response", "HTTP/1.0 500 Server error\r\n" "Connection: close\r\n" "\r\n", "HTTP", 1, 0, 500, "Server error", nullptr, "connection", "close", nullptr)); AFTER; }