diff options
| author | Joel Klinghed <the_jk@yahoo.com> | 2017-02-28 21:50:44 +0100 |
|---|---|---|
| committer | Joel Klinghed <the_jk@yahoo.com> | 2017-02-28 21:50:44 +0100 |
| commit | c029d90d1975e124d237605f1edb2be16bd05b5d (patch) | |
| tree | 9df87ffb365354bdb74a969440b32c8304bdbcb7 /src/http.cc | |
Initial commit
Diffstat (limited to 'src/http.cc')
| -rw-r--r-- | src/http.cc | 657 |
1 files changed, 657 insertions, 0 deletions
diff --git a/src/http.cc b/src/http.cc new file mode 100644 index 0000000..c043c87 --- /dev/null +++ b/src/http.cc @@ -0,0 +1,657 @@ +// -*- mode: c++; c-basic-offset: 2; -*- + +#include "common.hh" + +#include <cstring> +#include <memory> +#include <sstream> +#include <vector> + +#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<size_t> 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<size_t> const* const headers_; + std::vector<size_t>::const_iterator iter_; +}; + +class FilterHeaderIteratorImpl : public HeaderIteratorImpl { +public: + FilterHeaderIteratorImpl(char const* data, std::vector<size_t> 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<HeaderIterator> header() const override { + return std::unique_ptr<HeaderIterator>( + new HeaderIteratorImpl(data_, &headers_)); + } + std::unique_ptr<HeaderIterator> header( + std::string const& name) const override { + return std::unique_ptr<HeaderIterator>( + 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<size_t> 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<char[]> data_; +}; + +class SharedHttpResponse : public AbstractHttpResponse { +public: + SharedHttpResponse(std::shared_ptr<char> data, size_t offset, size_t len) + : AbstractHttpResponse(data.get() + offset, len), data_(data) { + } + +private: + std::shared_ptr<char> 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<char[]> data_; +}; + +class SharedHttpRequest : public AbstractHttpRequest { +public: + SharedHttpRequest(std::shared_ptr<char> data, size_t offset, size_t len) + : AbstractHttpRequest(data.get() + offset, len), data_(data) { + } + +private: + std::shared_ptr<char> 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<unsigned int>(version.major))); + data_.push_back('.'); + data_.append(tmp, snprintf( + tmp, 10, "%u", static_cast<unsigned int>(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<unsigned int>(version.major))); + data_.push_back('.'); + data_.append(tmp, snprintf( + tmp, 10, "%u", static_cast<unsigned int>(version.minor))); + data_.push_back(' '); + data_.append(tmp, snprintf( + tmp, 10, "%u", static_cast<unsigned int>(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<UniqueHttpResponse>( + new UniqueHttpResponse(data, len)); + if (ret->parse() == INCOMPLETE) return nullptr; + if (copy) ret->copy(); + return ret.release(); +} + +// static +HttpResponse* HttpResponse::parse(std::shared_ptr<char> data, + size_t offset, size_t len) { + auto ret = std::unique_ptr<SharedHttpResponse>( + 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<UniqueHttpRequest>( + new UniqueHttpRequest(data, len)); + if (ret->parse() == INCOMPLETE) return nullptr; + if (copy) ret->copy(); + return ret.release(); +} + +// static +HttpRequest* HttpRequest::parse(std::shared_ptr<char> data, + size_t offset, size_t len) { + auto ret = std::unique_ptr<SharedHttpRequest>( + 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); +} |
