// -*- mode: c++; c-basic-offset: 2; -*- #include "common.hh" #include #include #include #include #include "http.hh" namespace { std::string make_string(char const* data, size_t start, size_t end) { assert(start <= end); return std::string(data + start, end - start); } uint16_t number(char const* 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 char upper_ascii(char c) { return (c >= 'a' && c <= 'z') ? (c - 'a' + 'A') : c; } bool lower_equal(char const* data, size_t start, size_t end, std::string const& str) { assert(start <= end); if (str.size() != end - start) return false; for (auto i = str.begin(); start < end; ++start, ++i) { if (lower_ascii(*i) != lower_ascii(data[start])) return false; } return true; } bool lower_equal2(char const* data, size_t start, size_t end, char const* str, size_t size) { assert(start <= end); if (size != end - start) return false; for (auto i = str; start < end; ++start, ++i) { if (*i != lower_ascii(data[start])) return false; } return true; } bool allow_header_append(char const* data, size_t start, size_t end) { // These headers doesn't handle being merged with ',' even if the standard // say they must return !lower_equal2(data, start, end, "set-cookie", 10) && !lower_equal2(data, start, end, "set-cookie2", 11); } enum ParseResult { GOOD, BAD, INCOMPLETE, }; class HeaderIteratorImpl : public HeaderIterator { public: HeaderIteratorImpl(char const* data, std::vector const* headers) : data_(data), headers_(headers), iter_(headers_->begin()) { } bool valid() const override { return iter_ != headers_->end(); } std::string name() const override { return make_string(data_, iter_[0], iter_[1]); } bool name_equal(std::string const& name) const override { return lower_equal(data_, iter_[0], iter_[1], name); } std::string value() const override { std::string ret = make_string(data_, iter_[2], iter_[3]); if (allow_header_append(data_, iter_[0], iter_[1])) { auto i = iter_ + 4; while (i != headers_->end()) { if (i[0] != i[1]) break; ret.push_back(','); ret.append(data_ + 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: char const* const data_; std::vector const* const headers_; std::vector::const_iterator iter_; }; class FilterHeaderIteratorImpl : public HeaderIteratorImpl { public: FilterHeaderIteratorImpl(char const* data, std::vector const* headers, std::string const& filter) : HeaderIteratorImpl(data, headers), filter_(filter) { check_filter(); } void next() override { HeaderIteratorImpl::next(); check_filter(); } private: void check_filter() { while (true) { if (!valid() || name_equal(filter_)) return; next(); } } std::string const filter_; }; class AbstractHttp : public virtual Http { public: AbstractHttp(char const* data, size_t size) : data_(data), size_(size) { } std::string proto() const override { return make_string(data_, proto_start_, proto_slash_); } bool proto_equal(std::string const& proto) const override { return proto.compare(0, proto.size(), data_ + proto_start_, proto_slash_ - proto_start_) == 0; } 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::unique_ptr( new HeaderIteratorImpl(data_, &headers_)); } std::unique_ptr header( std::string const& name) const override { return std::unique_ptr( new FilterHeaderIteratorImpl(data_, &headers_, name)); } size_t size() const override { return content_start_; } protected: ParseResult parse_headers() { headers_.clear(); while (true) { auto start = content_start_; auto end = find_newline(start, &content_start_); 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_[content_start_ - 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(start, ':', end); if (colon == std::string::npos) return BAD; auto value_start = skip_lws(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; } void new_data(char const* data) { data_ = data; } char const* data() const { return data_; } size_t data_size() const { return size_; } size_t find(size_t start, char c, size_t end) const { assert(start <= end); for (; start < end; ++start) { if (data_[start] == c) return start; } return std::string::npos; } static bool is_lws(char c) { return c == ' ' || c == '\t'; } size_t skip_lws(size_t start, size_t end) const { assert(start <= end); while (start < end && is_lws(data_[start])) ++start; return start; } size_t find_newline(size_t start, size_t* next) const { assert(start <= size_); for (; start < size_; ++start) { if (data_[start] == '\r') { if (start + 1 < 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 valid_number(size_t start, size_t end) const { 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; } char const* data_; size_t const size_; size_t proto_start_; size_t proto_slash_; size_t proto_dot_; size_t proto_end_; std::vector headers_; size_t content_start_; }; class AbstractHttpResponse : public HttpResponse, protected AbstractHttp { public: AbstractHttpResponse(char const* data, size_t size) : AbstractHttp(data, size), good_(false) { } bool good() const override { return good_; } uint16_t status_code() const override { return number(data_, status_start_, status_end_); } std::string status_message() const override { return make_string(data_, status_msg_start_, status_msg_end_); } ParseResult parse() { good_ = false; status_msg_end_ = find_newline(0, &content_start_); if (status_msg_end_ == std::string::npos) return INCOMPLETE; proto_start_ = 0; proto_slash_ = find(0, '/', status_msg_end_); if (proto_slash_ == std::string::npos) return BAD; proto_dot_ = valid_number(proto_slash_ + 1, status_msg_end_); if (proto_dot_ == std::string::npos || data_[proto_dot_] != '.') { return BAD; } proto_end_ = valid_number(proto_dot_ + 1, status_msg_end_); if (proto_end_ == std::string::npos || !is_lws(data_[proto_end_])) { return BAD; } status_start_ = skip_lws(proto_end_ + 1, status_msg_end_); status_end_ = valid_number(status_start_, status_msg_end_); if (status_end_ == std::string::npos) return BAD; if (is_lws(data_[status_end_])) { status_msg_start_ = skip_lws(status_end_ + 1, status_msg_end_); } else { status_msg_start_ = status_end_; if (status_msg_start_ != status_msg_end_) return BAD; } auto ret = parse_headers(); if (ret == GOOD) good_ = true; return ret; } protected: bool good_; size_t status_start_; size_t status_end_; size_t status_msg_start_; size_t status_msg_end_; }; class UniqueHttpResponse : public AbstractHttpResponse { public: UniqueHttpResponse(char const* data, size_t size) : AbstractHttpResponse(data, size) { } void copy() { assert(!data_); auto tmp = new char[data_size()]; memcpy(tmp, data(), data_size()); new_data(tmp); data_.reset(tmp); } private: std::unique_ptr data_; }; class SharedHttpResponse : public AbstractHttpResponse { public: SharedHttpResponse(std::shared_ptr data, size_t offset, size_t len) : AbstractHttpResponse(data.get() + offset, len), data_(data) { } private: std::shared_ptr data_; }; class AbstractHttpRequest : public HttpRequest, protected AbstractHttp { public: AbstractHttpRequest(char const* data, size_t size) : AbstractHttp(data, size), good_(false) { } bool good() const override { return good_; } std::string method() const override { return make_string(data_, 0, method_end_); } bool method_equal(std::string const& method) const override { return method.compare(0, method.size(), data_, method_end_) == 0; } std::string url() const override { return make_string(data_, url_start_, url_end_); } ParseResult parse() { good_ = false; proto_end_ = find_newline(0, &content_start_); if (proto_end_ == std::string::npos) return INCOMPLETE; method_end_ = 0; while (method_end_ < proto_end_ && !is_lws(data_[method_end_])) { ++method_end_; } if (method_end_ == 0 || method_end_ == proto_end_) return BAD; url_start_ = skip_lws(method_end_ + 1, proto_end_); 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 BAD; proto_start_ = skip_lws(url_end_ + 1, proto_end_); proto_slash_ = find(proto_start_, '/', proto_end_); if (proto_slash_ == std::string::npos) return BAD; proto_dot_ = valid_number(proto_slash_ + 1, proto_end_); if (proto_dot_ == std::string::npos || data_[proto_dot_] != '.') { return BAD; } auto tmp = valid_number(proto_dot_ + 1, proto_end_); if (tmp != proto_end_) return BAD; auto ret = parse_headers(); if (ret == GOOD) good_ = true; return ret; } protected: bool good_; size_t method_end_; size_t url_start_; size_t url_end_; }; class UniqueHttpRequest : public AbstractHttpRequest { public: UniqueHttpRequest(char const* data, size_t size) : AbstractHttpRequest(data, size) { } void copy() { assert(!data_); auto tmp = new char[data_size()]; memcpy(tmp, data(), data_size()); new_data(tmp); data_.reset(tmp); } private: std::unique_ptr data_; }; class SharedHttpRequest : public AbstractHttpRequest { public: SharedHttpRequest(std::shared_ptr data, size_t offset, size_t len) : AbstractHttpRequest(data.get() + offset, len), data_(data) { } private: std::shared_ptr data_; }; class AbstractHttpBuilder { public: AbstractHttpBuilder() : set_content_length_(false), set_content_(false) { } void add_header(std::string const& name, std::string const& value) { if (name.empty()) { data_.push_back(' '); data_.append(value); data_.append("\r\n", 2); return; } size_t pos = 0; auto c = upper_ascii(name[pos]); if (c != name[pos]) { data_.push_back(c); ++pos; } auto last = pos; for (; pos < name.size(); ++pos) { if (name[pos] == '-') { ++pos; if (pos == name.size()) break; data_.append(name.data() + last, pos - last); auto c = upper_ascii(name[pos]); if (c != name[pos]) { data_.push_back(c); ++pos; } last = pos; } } if (!set_content_length_ && lower_equal2(name.data(), 0, name.size(), "content-length", 14)) { set_content_length_ = true; } if (last < pos) { data_.append(name.data() + last, pos - last); } data_.append(": ", 2); data_.append(value); data_.append("\r\n", 2); } void set_content(std::string const& content) { set_content_ = true; content_ = content; } std::string build() const { std::string ret(data_); if (!set_content_length_ && set_content_) { char tmp[50]; auto len = snprintf(tmp, sizeof(tmp), "Content-Length: %zu\r\n", content_.size()); ret.append(tmp, len); } ret.append("\r\n", 2); if (set_content_) { ret.append(content_); } return ret; } protected: std::string data_; std::string content_; bool set_content_length_; bool set_content_; }; class HttpRequestBuilderImpl : public HttpRequestBuilder, AbstractHttpBuilder { public: HttpRequestBuilderImpl(std::string const& method, std::string const& url, std::string const& proto, Version version) { data_.append(method); data_.push_back(' '); data_.append(url); data_.push_back(' '); data_.append(proto); data_.push_back('/'); char tmp[10]; data_.append(tmp, snprintf( tmp, 10, "%u", static_cast(version.major))); data_.push_back('.'); data_.append(tmp, snprintf( tmp, 10, "%u", static_cast(version.minor))); data_.append("\r\n", 2); } void add_header(std::string const& name, std::string const& value) override { AbstractHttpBuilder::add_header(name, value); } void set_content(std::string const& content) override { AbstractHttpBuilder::set_content(content); } std::string build() const override { return AbstractHttpBuilder::build(); } }; class HttpResponseBuilderImpl : public HttpResponseBuilder, AbstractHttpBuilder { public: HttpResponseBuilderImpl(std::string const& proto, Version version, uint16_t status_code, std::string const& status) { data_.append(proto); data_.push_back('/'); char tmp[10]; data_.append(tmp, snprintf( tmp, 10, "%u", static_cast(version.major))); data_.push_back('.'); data_.append(tmp, snprintf( tmp, 10, "%u", static_cast(version.minor))); data_.push_back(' '); data_.append(tmp, snprintf( tmp, 10, "%u", static_cast(status_code))); data_.push_back(' '); data_.append(status); data_.append("\r\n", 2); } void add_header(std::string const& name, std::string const& value) override { AbstractHttpBuilder::add_header(name, value); } void set_content(std::string const& content) override { AbstractHttpBuilder::set_content(content); } std::string build() const override { return AbstractHttpBuilder::build(); } }; } // namespace // static HttpResponse* HttpResponse::parse(std::string const& data) { return parse(data.data(), data.size(), true); } // static HttpResponse* HttpResponse::parse(char const* data, size_t len, bool copy) { auto ret = std::unique_ptr( new UniqueHttpResponse(data, len)); if (ret->parse() == INCOMPLETE) return nullptr; if (copy) ret->copy(); return ret.release(); } // static HttpResponse* HttpResponse::parse(std::shared_ptr data, size_t offset, size_t len) { auto ret = std::unique_ptr( new SharedHttpResponse(data, offset, len)); if (ret->parse() == INCOMPLETE) return nullptr; return ret.release(); } std::string Http::first_header(std::string const& name) const { static std::string empty_str; auto iter = header(name); if (iter->valid()) { return iter->value(); } return empty_str; } // static HttpRequest* HttpRequest::parse(std::string const& data) { return parse(data.data(), data.size(), true); } // static HttpRequest* HttpRequest::parse(char const* data, size_t len, bool copy) { auto ret = std::unique_ptr( new UniqueHttpRequest(data, len)); if (ret->parse() == INCOMPLETE) return nullptr; if (copy) ret->copy(); return ret.release(); } // static HttpRequest* HttpRequest::parse(std::shared_ptr data, size_t offset, size_t len) { auto ret = std::unique_ptr( new SharedHttpRequest(data, offset, len)); if (ret->parse() == INCOMPLETE) return nullptr; return ret.release(); } // static HttpRequestBuilder* HttpRequestBuilder::create(std::string const& method, std::string const& url, std::string const& proto, Version version) { return new HttpRequestBuilderImpl(method, url, proto, version); } // static HttpResponseBuilder* HttpResponseBuilder::create(std::string const& proto, Version version, uint16_t status_code, std::string const& status) { return new HttpResponseBuilderImpl(proto, version, status_code, status); }