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