summaryrefslogtreecommitdiff
path: root/src/http.cc
diff options
context:
space:
mode:
authorJoel Klinghed <the_jk@yahoo.com>2017-02-28 21:50:44 +0100
committerJoel Klinghed <the_jk@yahoo.com>2017-02-28 21:50:44 +0100
commitc029d90d1975e124d237605f1edb2be16bd05b5d (patch)
tree9df87ffb365354bdb74a969440b32c8304bdbcb7 /src/http.cc
Initial commit
Diffstat (limited to 'src/http.cc')
-rw-r--r--src/http.cc657
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);
+}