#include "common.hh" #include "http_protocol.hh" #include #include #include #include namespace { uint16_t number(std::string_view data, size_t start, size_t end) { uint16_t ret = 0; assert(start < end); for (; start < end; ++start) { ret *= 10; ret += data[start] - '0'; } return ret; } inline char lower_ascii(char c) { return (c >= 'A' && c <= 'Z') ? (c - 'A' + 'a') : c; } inline bool is_lws(char c) { return c == ' ' || c == '\t'; } inline bool is_char(char c) { return !(c & 0x80); } inline bool is_ctl(char c) { return c < ' ' || c == 0x7f; } inline bool is_separator(char c) { return is_lws(c) || c == '(' || c == ')' || c == '<' || c == '>' || c == '@' || c == ',' || c == ';' || c == ':' || c == '\\' || c == '\"' || c == '/' || c == '[' || c == ']' || c == '?' || c == '=' || c == '{' || c == '}'; } inline bool is_token(char c) { return is_char(c) && !is_ctl(c) && !is_separator(c); } void make_lowercase(std::string& data, size_t start, size_t end) { for (size_t i = start; i <= end; ++i) { char lower = lower_ascii(data[i]); if (lower != data[i]) data[i] = lower; } } bool allow_header_append(std::string_view name) { // These headers doesn't handle being merged with ',' even if the standard // say they must return !(name == "set-cookie" || name == "set-cookie2"); } enum ParseResult { GOOD, BAD, INCOMPLETE, }; class HeaderIteratorImpl : public HeaderIterator { public: HeaderIteratorImpl(std::string_view data, std::vector const* headers) : data_(data), headers_(headers), iter_(headers_->begin()) { } bool valid() const override { return iter_ != headers_->end(); } std::string_view name() const override { return data_.substr(iter_[0], iter_[1] - iter_[0]); } std::string value() const override { std::string ret(data_.substr(iter_[2], iter_[3] - iter_[2])); if (allow_header_append(name())) { auto i = iter_ + 4; while (i != headers_->end()) { if (i[0] != i[1]) break; ret.push_back(','); ret.append(data_.substr(i[2], i[3] - i[2])); i += 4; } } return ret; } void next() override { if (iter_ != headers_->end()) { while (true) { iter_ += 4; if (iter_ == headers_->end() || iter_[0] != iter_[1]) break; } } } private: std::string_view const data_; std::vector const* const headers_; std::vector::const_iterator iter_; }; class FilterHeaderIteratorImpl : public HeaderIteratorImpl { public: FilterHeaderIteratorImpl(std::string_view data, std::vector const* headers, std::string_view filter) : HeaderIteratorImpl(data, headers), filter_(filter) { check_filter(); } void next() override { HeaderIteratorImpl::next(); check_filter(); } private: void check_filter() { while (true) { if (!valid() || name() == filter_) return; HeaderIteratorImpl::next(); } } std::string_view const filter_; }; class HeaderTokenIteratorImpl : public HeaderTokenIterator { public: explicit HeaderTokenIteratorImpl(std::unique_ptr&& header) : header_(std::move(header)), start_(0), middle_(0), end_(0) { check_token(); } bool valid() const override { return header_->valid(); } std::string token() const override { return header_->value().substr(start_, middle_ - start_); } void next() override { start_ = end_; check_token(); } private: static size_t skip_lws(std::string const& str, size_t pos) { while (pos < str.size() && is_lws(str[pos])) ++pos; return pos; } static size_t skip_token(std::string const& str, size_t pos) { assert(is_token(str[pos])); ++pos; while (pos < str.size() && is_token(str[pos])) ++pos; return pos; } static size_t skip_quoted(std::string const& str, size_t pos) { assert(str[pos] == '"'); ++pos; while (pos < str.size()) { if (str[pos] == '\\') { pos += 2; } else if (str[pos] == '\"') { ++pos; break; } else { ++pos; } } return pos; } void check_token() { while (true) { if (!header_->valid()) return; auto const& value = header_->value(); start_ = skip_lws(value, start_); if (start_ >= value.size()) { header_->next(); start_ = 0; continue; } if (!is_token(value[start_])) { if (value[start_] != ';') { ++start_; while (start_ < value.size() && !(is_lws(value[start_]) || value[start_] == ',' || value[start_] == ';')) { ++start_; } if (start_ < value.size() && value[start_] != ';') { continue; } } // This will cause us to loop again after paramters // are read middle_ = start_; } else { middle_ = skip_token(value, start_); } end_ = middle_; while (true) { end_ = skip_lws(value, end_); if (end_ == value.size() || value[end_] != ';') break; end_ = skip_lws(value, end_ + 1); if (!is_token(value[end_])) { while (end_ < value.size() && !is_separator(value[end_])) ++end_; continue; } end_ = skip_token(value, end_); end_ = skip_lws(value, end_); if (end_ == value.size() || value[end_] != '=') break; end_ = skip_lws(value, end_ + 1); if (end_ < value.size() && value[end_] == '"') { end_ = skip_quoted(value, end_); } else { if (!is_token(value[end_])) { while (end_ < value.size() && !is_separator(value[end_])) ++end_; continue; } end_ = skip_token(value, end_); } } if (end_ < value.size() && value[end_] == ',') ++end_; if (start_ < middle_) return; start_ = end_; } } std::unique_ptr header_; size_t start_; size_t middle_; size_t end_; }; size_t find_newline(std::string_view data, size_t start, size_t* next) { assert(start <= data.size()); for (; start < data.size(); ++start) { if (data[start] == '\r') { if (start + 1 < data.size() && data[start + 1] == '\n') { if (next) *next = start + 2; } else { if (next) *next = start + 1; } return start; } else if (data[start] == '\n') { if (next) *next = start + 1; return start; } } return std::string::npos; } size_t find(std::string_view data, size_t start, char c, size_t end) { assert(start <= end); for (; start < end; ++start) { if (data[start] == c) return start; } return std::string::npos; } size_t skip_lws(std::string_view data, size_t start, size_t end) { assert(start <= end); while (start < end && is_lws(data[start])) ++start; return start; } size_t valid_number(std::string_view data, size_t start, size_t end) { assert(start <= end); if (start == end) return std::string::npos; if (data[start] == '0') { return start + 1; } if (data[start] < '0' || data[start] > '9') return std::string::npos; for (++start; start < end; ++start) { if (data[start] < '0' || data[start] > '9') break; } return start; } ParseResult parse_headers(std::string_view data, size_t* offset, std::vector* headers) { assert(*offset <= data.size()); assert(headers->empty()); while (true) { auto start = *offset; auto end = find_newline(data, start, offset); if (end == std::string::npos) return INCOMPLETE; if (end == start) { // The final newline can only be a alone '\r' if the one in front of // it is also '\r', otherwise we expect a missing '\n' if (data[start - 1] == '\n' && data[*offset - 1] == '\r') { return INCOMPLETE; } break; } if (is_lws(data[start])) { if (headers->empty()) return BAD; headers->push_back(start); headers->push_back(start); headers->push_back(start + 1); headers->push_back(end); } else { auto colon = find(data, start, ':', end); if (colon == std::string::npos) return BAD; auto value_start = skip_lws(data, colon + 1, end); while (colon > start && is_lws(data[colon - 1])) --colon; headers->push_back(start); headers->push_back(colon); headers->push_back(value_start); headers->push_back(end); } } return GOOD; } std::string make_lowercase_header_names(std::string_view data, std::vector const& headers) { std::string ret(data); for (size_t i = 0; i < headers.size(); i += 4) { make_lowercase(ret, headers[i], headers[i + 1]); } return ret; } class AbstractHttp : public virtual HttpPackage { public: AbstractHttp(std::string data, bool good, size_t proto_start, size_t proto_slash, size_t proto_dot, size_t proto_end, std::vector headers, size_t content_start) : data_(std::move(data)), good_(good), proto_start_(proto_start), proto_slash_(proto_slash), proto_dot_(proto_dot), proto_end_(proto_end), headers_(std::move(headers)), content_start_(content_start) { } bool good() const override { return good_; } std::string_view proto() const override { return std::string_view(data_).substr( proto_start_, proto_slash_ - proto_start_); } Version proto_version() const override { Version ret; ret.major = number(data_, proto_slash_ + 1, proto_dot_); ret.minor = number(data_, proto_dot_ + 1, proto_end_); return ret; } std::unique_ptr header() const override { return std::make_unique(data_, &headers_); } std::unique_ptr header( std::string_view name) const override { return std::make_unique(data_, &headers_, name); } std::unique_ptr header_tokens(std::string_view name) const override { return std::make_unique(header(name)); } size_t size() const override { return content_start_; } protected: std::string const data_; bool const good_; size_t const proto_start_; size_t const proto_slash_; size_t const proto_dot_; size_t const proto_end_; std::vector const headers_; size_t const content_start_; }; class HttpResponseImpl : public HttpResponse, protected AbstractHttp { public: HttpResponseImpl(std::string data, bool good, size_t proto_start, size_t proto_slash, size_t proto_dot, size_t proto_end, size_t status_start, size_t status_end, size_t status_msg_start, size_t status_msg_end, std::vector headers, size_t content_start) : AbstractHttp(std::move(data), good, proto_start, proto_slash, proto_dot, proto_end, std::move(headers), content_start), status_start_(status_start), status_end_(status_end), status_msg_start_(status_msg_start), status_msg_end_(status_msg_end) { } uint16_t status_code() const override { return number(data_, status_start_, status_end_); } std::string_view status_message() const override { return std::string_view(data_).substr(status_msg_start_, status_msg_end_ - status_msg_start_); } static std::unique_ptr parse(std::string_view data) { size_t content_start = 0; size_t status_msg_end = find_newline(data, 0, &content_start); if (status_msg_end == std::string::npos) return nullptr; size_t proto_start = 0; size_t proto_slash = find(data, 0, '/', status_msg_end); if (proto_slash == std::string::npos) return make_bad_http_response(); size_t proto_dot = valid_number(data, proto_slash + 1, status_msg_end); if (proto_dot == std::string::npos || data[proto_dot] != '.') return make_bad_http_response(); size_t proto_end = valid_number(data, proto_dot + 1, status_msg_end); if (proto_end == std::string::npos || !is_lws(data[proto_end])) return make_bad_http_response(); size_t status_start = skip_lws(data, proto_end + 1, status_msg_end); size_t status_end = valid_number(data, status_start, status_msg_end); if (status_end == std::string::npos) return make_bad_http_response(); size_t status_msg_start; if (is_lws(data[status_end])) { status_msg_start = skip_lws(data, status_end + 1, status_msg_end); } else { status_msg_start = status_end; if (status_msg_start != status_msg_end) return make_bad_http_response(); } std::vector headers; switch (parse_headers(data, &content_start, &headers)) { case GOOD: return std::make_unique( make_lowercase_header_names(data, headers), true, proto_start, proto_slash, proto_dot, proto_end, status_start, status_end, status_msg_start, status_msg_end, std::move(headers), content_start); case BAD: return make_bad_http_response(); case INCOMPLETE: return nullptr; } assert(false); return nullptr; } private: static std::unique_ptr make_bad_http_response() { return std::make_unique(std::string(), false, 0, 0, 0, 0, 0, 0, 0, 0, std::vector(), 0); } size_t const status_start_; size_t const status_end_; size_t const status_msg_start_; size_t const status_msg_end_; }; class HttpRequestImpl : public HttpRequest, protected AbstractHttp { public: HttpRequestImpl(std::string data, bool good, size_t method_end, size_t url_start, size_t url_end, size_t proto_start, size_t proto_slash, size_t proto_dot, size_t proto_end, std::vector headers, size_t content_start) : AbstractHttp(std::move(data), good, proto_start, proto_slash, proto_dot, proto_end, std::move(headers), content_start), method_end_(method_end), url_start_(url_start), url_end_(url_end) { } std::string_view method() const override { return std::string_view(data_).substr(0, method_end_); } std::string_view url() const override { return std::string_view(data_).substr(url_start_, url_end_ - url_start_); } static std::unique_ptr parse(std::string_view data) { size_t content_start = 0; size_t proto_end = find_newline(data, 0, &content_start); if (proto_end == std::string::npos) return nullptr; size_t method_end = 0; while (method_end < proto_end && !is_lws(data[method_end])) { ++method_end; } if (method_end == 0 || method_end == proto_end) return make_bad_request(); size_t url_start = skip_lws(data, method_end + 1, proto_end); size_t url_end = url_start; while (url_end < proto_end && !is_lws(data[url_end])) ++url_end; if (url_end == url_start || url_end == proto_end) return make_bad_request(); size_t proto_start = skip_lws(data, url_end + 1, proto_end); size_t proto_slash = find(data, proto_start, '/', proto_end); if (proto_slash == std::string::npos) return make_bad_request(); size_t proto_dot = valid_number(data, proto_slash + 1, proto_end); if (proto_dot == std::string::npos || data[proto_dot] != '.') return make_bad_request(); auto tmp = valid_number(data, proto_dot + 1, proto_end); if (tmp != proto_end) return make_bad_request(); std::vector headers; switch (parse_headers(data, &content_start, &headers)) { case GOOD: return std::make_unique( make_lowercase_header_names(data, headers), true, method_end, url_start, url_end, proto_start, proto_slash, proto_dot, proto_end, std::move(headers), content_start); case BAD: return make_bad_request(); case INCOMPLETE: return nullptr; } assert(false); return nullptr; } private: static std::unique_ptr make_bad_request() { return std::make_unique("", false, 0, 0, 0, 0, 0, 0, 0, std::vector(), 0); } size_t const method_end_; size_t const url_start_; size_t const url_end_; }; class AbstractHttpBuilder { public: void add_header(std::string name, std::string value) { headers_.emplace_back(std::move(name), std::move(value)); } bool build(Buffer* dst) const { for (auto const& pair : headers_) { if (pair.first.empty()) { if (!append(dst, " ")) return false; } else { if (!append(dst, pair.first) || !append(dst, ": ")) return false; } if (!append(dst, pair.second) || !append(dst, "\r\n")) return false; } return append(dst, "\r\n"); } size_t size() const { size_t ret = 0; for (auto const& pair : headers_) { if (pair.first.empty()) { ++ret; // ' ' } else { ret += pair.first.size() + 2; // ": " } ret += pair.second.size() + 2; // \r\” } return ret + 2; // \r\n } protected: bool append(Buffer* dst, std::string_view str) const { return Buffer::write(dst, str.data(), str.size()) == str.size(); } std::vector> headers_; }; class HttpRequestBuilderImpl : public HttpRequestBuilder, AbstractHttpBuilder { public: HttpRequestBuilderImpl(std::string method, std::string url, std::string proto, Version version) : method_(std::move(method)), url_(std::move(url)), proto_(std::move(proto)), version_(version) {} void add_header(std::string name, std::string value) override { AbstractHttpBuilder::add_header(std::move(name), std::move(value)); } bool build(Buffer* dst) const override { if (!append(dst, method_) || !append(dst, " ") || !append(dst, url_) || !append(dst, " ") || !append(dst, proto_) || !append(dst, "/")) return false; char tmp[10]; auto len = snprintf(tmp, sizeof(tmp), "%u", static_cast(version_.major)); if (!append(dst, std::string_view(tmp, len))) return false; if (!append(dst, ".")) return false; len = snprintf(tmp, sizeof(tmp), "%u", static_cast(version_.minor)); if (!append(dst, std::string_view(tmp, len))) return false; if (!append(dst, "\r\n")) return false; return AbstractHttpBuilder::build(dst); } size_t size() const override { size_t ret = 0; ret += method_.size() + 1 + url_.size() + 1 + proto_.size() + 1; char tmp[10]; auto len = snprintf(tmp, sizeof(tmp), "%u", static_cast(version_.major)); ret += len; ++ret; // '.' len = snprintf(tmp, sizeof(tmp), "%u", static_cast(version_.minor)); ret += len; ret += 2; // \r\n return ret + AbstractHttpBuilder::size(); } private: std::string const method_; std::string const url_; std::string const proto_; Version const version_; }; class HttpResponseBuilderImpl : public HttpResponseBuilder, AbstractHttpBuilder { public: HttpResponseBuilderImpl(std::string proto, Version version, uint16_t status_code, std::string status) : proto_(std::move(proto)), version_(version), status_code_(status_code), status_(std::move(status)) { } void add_header(std::string name, std::string value) override { AbstractHttpBuilder::add_header(std::move(name), std::move(value)); } bool build(Buffer* dst) const override { if (!append(dst, proto_) || !append(dst, "/")) return false; char tmp[10]; auto len = snprintf(tmp, sizeof(tmp), "%u", static_cast(version_.major)); if (!append(dst, std::string_view(tmp, len)) || !append(dst, ".")) return false; len = snprintf(tmp, sizeof(tmp), "%u", static_cast(version_.minor)); if (!append(dst, std::string_view(tmp, len)) || !append(dst, " ")) return false; len = snprintf(tmp, sizeof(tmp), "%u", static_cast(status_code_)); if (!append(dst, std::string_view(tmp, len)) || !append(dst, " ") || !append(dst, status_) || !append(dst, "\r\n")) return false; return AbstractHttpBuilder::build(dst); } size_t size() const override { size_t ret = 0; ret += proto_.size(); ++ret; // "/" char tmp[10]; auto len = snprintf(tmp, sizeof(tmp), "%u", static_cast(version_.major)); ret += len; ++ret; // "." len = snprintf(tmp, sizeof(tmp), "%u", static_cast(version_.minor)); ret += len; ++ret; // " " len = snprintf(tmp, sizeof(tmp), "%u", static_cast(status_code_)); ret += len; ++ret; // " " ret += status_.length(); ret += 2; // \r\n return ret + AbstractHttpBuilder::size(); } private: std::string const proto_; Version const version_; uint16_t const status_code_; std::string const status_; }; class CgiResponseBuilderImpl : public CgiResponseBuilder, AbstractHttpBuilder { public: explicit CgiResponseBuilderImpl(uint16_t status_code) { AbstractHttpBuilder::add_header("Status", std::to_string(status_code)); } void add_header(std::string name, std::string value) override { AbstractHttpBuilder::add_header(std::move(name), std::move(value)); } bool build(Buffer* dst) const override { return AbstractHttpBuilder::build(dst); } size_t size() const override { return AbstractHttpBuilder::size(); } }; } // namespace // static std::unique_ptr HttpResponse::parse(RoBuffer* buffer) { size_t want = 1024; size_t last_avail = 0; while (true) { size_t avail; auto* rptr = buffer->rbuf(want, avail); if (avail == last_avail) return nullptr; last_avail = avail; auto resp = HttpResponseImpl::parse(std::string_view(rptr, avail)); if (resp) { buffer->rcommit(resp->size()); return resp; } want = avail + 1024; } } std::string HttpPackage::first_header(std::string_view name) const { static std::string empty_str; auto iter = header(name); if (iter->valid()) { return iter->value(); } return empty_str; } // static std::unique_ptr HttpRequest::parse(RoBuffer* buffer) { size_t want = 1024; size_t last_avail = 0; while (true) { size_t avail; auto* rptr = buffer->rbuf(want, avail); if (avail == last_avail) return nullptr; last_avail = avail; auto req = HttpRequestImpl::parse(std::string_view(rptr, avail)); if (req) { buffer->rcommit(req->size()); return req; } want = avail + 1024; } } // static std::unique_ptr HttpRequestBuilder::create( std::string method, std::string url, std::string proto, Version version) { return std::make_unique(std::move(method), std::move(url), std::move(proto), version); } // static std::unique_ptr HttpResponseBuilder::create( std::string proto, Version version, uint16_t status_code, std::string status) { return std::make_unique(std::move(proto), version, status_code, std::move(status)); } // static std::unique_ptr CgiResponseBuilder::create( uint16_t status_code) { return std::make_unique(status_code); } std::string_view http_standard_message(uint16_t code) { switch (code) { case 100: return "Continue"; case 101: return "Switching Protocols"; case 200: return "OK"; case 201: return "Created"; case 202: return "Accepted"; case 203: return "Non-Authorative Information"; case 204: return "No Content"; case 205: return "Reset Content"; case 206: return "Partial Content"; case 300: return "Multiple Choices"; case 301: return "Moved Permanently"; case 302: return "Found"; case 303: return "See Other"; case 304: return "Not Modified"; case 305: return "Use Proxy"; case 307: return "Temporary Redirect"; case 400: return "Bad Request"; case 401: return "Unauthorized"; case 402: return "Payment Required"; case 403: return "Forbidden"; case 404: return "Not Found"; case 405: return "Method Not Allowed"; case 406: return "Not Acceptable"; case 407: return "Proxy Authentication Required"; case 408: return "Request Timeout"; case 409: return "Conflict"; case 410: return "Gone"; case 411: return "Length Required"; case 412: return "Precondition Failed"; case 413: return "Request Entity Too Large"; case 414: return "Request-URI Too Long"; case 415: return "Unsupported Media Type"; case 416: return "Requested Range Not Satisfiable"; case 417: return "Expectation Failed"; case 500: return "Internal Server Error"; case 501: return "Not Implemented"; case 502: return "Bad Gateway"; case 503: return "Service Unavailable"; case 504: return "Gateway Timeout"; case 505: return "HTTP Version Not Supported"; } return ""; } std::string http_date(time_t in) { char tmp[50]; auto len = strftime(tmp, sizeof(tmp), "%a, %d %b %Y %H:%M:%S GMT", gmtime(&in)); return std::string(tmp, len); }