diff options
Diffstat (limited to 'test/test-http.cc')
| -rw-r--r-- | test/test-http.cc | 477 |
1 files changed, 477 insertions, 0 deletions
diff --git a/test/test-http.cc b/test/test-http.cc new file mode 100644 index 0000000..4b1b62f --- /dev/null +++ b/test/test-http.cc @@ -0,0 +1,477 @@ +// -*- mode: c++; c-basic-offset: 2; -*- + +#include "common.hh" +#include "test.hh" + +#include <cstdarg> +#include <cstring> + +#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<HttpResponse> 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<HttpRequest> 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<HttpResponse> 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<HttpResponse> 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<HttpRequestBuilder> 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<HttpResponseBuilder> 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" + "<html><head><title></title></head><body></body></html>", + "HTTP", 1, 1, 200, "OK", + "<html><head><title></title></head><body></body></html>", + "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; +} |
