summaryrefslogtreecommitdiff
path: root/src/http_protocol.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/http_protocol.cc')
-rw-r--r--src/http_protocol.cc916
1 files changed, 916 insertions, 0 deletions
diff --git a/src/http_protocol.cc b/src/http_protocol.cc
new file mode 100644
index 0000000..c0e40ad
--- /dev/null
+++ b/src/http_protocol.cc
@@ -0,0 +1,916 @@
+#include "common.hh"
+
+#include "http_protocol.hh"
+
+#include <memory>
+#include <string.h>
+#include <time.h>
+#include <vector>
+
+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<size_t> 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<size_t> const* const headers_;
+ std::vector<size_t>::const_iterator iter_;
+};
+
+class FilterHeaderIteratorImpl : public HeaderIteratorImpl {
+public:
+ FilterHeaderIteratorImpl(std::string_view data,
+ std::vector<size_t> 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<HeaderIterator>&& 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<HeaderIterator> 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<size_t>* 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<size_t> 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<size_t> 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<HeaderIterator> header() const override {
+ return std::make_unique<HeaderIteratorImpl>(data_, &headers_);
+ }
+ std::unique_ptr<HeaderIterator> header(
+ std::string_view name) const override {
+ return std::make_unique<FilterHeaderIteratorImpl>(data_, &headers_, name);
+ }
+
+ std::unique_ptr<HeaderTokenIterator> header_tokens(std::string_view name)
+ const override {
+ return std::make_unique<HeaderTokenIteratorImpl>(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<size_t> 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<size_t> 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<HttpResponse> 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<size_t> headers;
+ switch (parse_headers(data, &content_start, &headers)) {
+ case GOOD:
+ return std::make_unique<HttpResponseImpl>(
+ 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<HttpResponse> make_bad_http_response() {
+ return std::make_unique<HttpResponseImpl>(std::string(), false,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ std::vector<size_t>(), 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<size_t> 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<HttpRequest> 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<size_t> headers;
+ switch (parse_headers(data, &content_start, &headers)) {
+ case GOOD:
+ return std::make_unique<HttpRequestImpl>(
+ 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<HttpRequest> make_bad_request() {
+ return std::make_unique<HttpRequestImpl>("", false, 0, 0, 0, 0, 0, 0, 0,
+ std::vector<size_t>(), 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<std::pair<std::string, std::string>> 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<unsigned int>(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<unsigned int>(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<unsigned int>(version_.major));
+ ret += len;
+ ++ret; // '.'
+ len = snprintf(tmp, sizeof(tmp), "%u",
+ static_cast<unsigned int>(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<unsigned int>(version_.major));
+ if (!append(dst, std::string_view(tmp, len)) ||
+ !append(dst, "."))
+ return false;
+ len = snprintf(tmp, sizeof(tmp), "%u",
+ static_cast<unsigned int>(version_.minor));
+ if (!append(dst, std::string_view(tmp, len)) ||
+ !append(dst, " "))
+ return false;
+ len = snprintf(tmp, sizeof(tmp), "%u",
+ static_cast<unsigned int>(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<unsigned int>(version_.major));
+ ret += len;
+ ++ret; // "."
+ len = snprintf(tmp, sizeof(tmp), "%u",
+ static_cast<unsigned int>(version_.minor));
+ ret += len;
+ ++ret; // " "
+ len = snprintf(tmp, sizeof(tmp), "%u",
+ static_cast<unsigned int>(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> 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> 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> HttpRequestBuilder::create(
+ std::string method,
+ std::string url,
+ std::string proto,
+ Version version) {
+ return std::make_unique<HttpRequestBuilderImpl>(std::move(method),
+ std::move(url),
+ std::move(proto), version);
+}
+
+// static
+std::unique_ptr<HttpResponseBuilder> HttpResponseBuilder::create(
+ std::string proto,
+ Version version,
+ uint16_t status_code,
+ std::string status) {
+ return std::make_unique<HttpResponseBuilderImpl>(std::move(proto), version,
+ status_code,
+ std::move(status));
+}
+
+// static
+std::unique_ptr<CgiResponseBuilder> CgiResponseBuilder::create(
+ uint16_t status_code) {
+ return std::make_unique<CgiResponseBuilderImpl>(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);
+}