/*
    This file is part of TON Blockchain Library.
    TON Blockchain Library is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation, either version 2 of the License, or
    (at your option) any later version.
    TON Blockchain Library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Lesser General Public License for more details.
    You should have received a copy of the GNU Lesser General Public License
    along with TON Blockchain Library.  If not, see .
    Copyright 2019-2020 Telegram Systems LLP
*/
#include "http.h"
#include "td/utils/misc.h"
#include 
namespace ton {
namespace http {
namespace util {
td::Result get_line(td::ChainBufferReader &input, std::string &cur_line, bool &read,
                                 size_t max_line_size) {
  while (true) {
    if (input.size() == 0) {
      read = false;
      return "";
    }
    auto S = input.prepare_read();
    auto f = S.find('\n');
    if (f == td::Slice::npos) {
      if (cur_line.size() + S.size() > max_line_size) {
        return td::Status::Error("too big http header");
      }
      cur_line += S.str();
      input.confirm_read(S.size());
      continue;
    }
    if (f > 0) {
      if (S[f - 1] == '\r') {
        cur_line += S.truncate(f - 1).str();
      } else {
        cur_line += S.truncate(f).str();
      }
    } else {
      if (cur_line.size() > 0 && cur_line[cur_line.size() - 1] == '\r') {
        cur_line = cur_line.substr(0, cur_line.size() - 1);
      }
    }
    input.confirm_read(f + 1);
    auto s = std::move(cur_line);
    cur_line = "";
    read = true;
    return s;
  }
}
td::Result get_header(std::string line) {
  auto p = line.find(':');
  if (p == std::string::npos) {
    return td::Status::Error("failed to parse header");
  }
  return HttpHeader{line.substr(0, p), td::trim(line.substr(p + 1))};
}
}  // namespace util
void HttpHeader::store_http(td::ChainBufferWriter &output) {
  output.append(name);
  output.append(": ");
  output.append(value);
  output.append("\r\n");
}
tl_object_ptr HttpHeader::store_tl() {
  return create_tl_object(name, value);
}
td::Result> HttpRequest::parse(std::unique_ptr request, std::string &cur_line,
                                                            bool &exit_loop, td::ChainBufferReader &input) {
  exit_loop = false;
  CHECK(!request || !request->check_parse_header_completed());
  while (true) {
    bool read;
    TRY_RESULT(line, util::get_line(input, cur_line, read, HttpRequest::max_one_header_size()));
    if (!read) {
      exit_loop = true;
      break;
    }
    if (!request) {
      auto v = td::full_split(line);
      if (v.size() != 3) {
        return td::Status::Error("expected http header in form ");
      }
      TRY_RESULT_ASSIGN(request, HttpRequest::create(v[0], v[1], v[2]));
    } else {
      if (line.size() == 0) {
        TRY_STATUS(request->complete_parse_header());
        break;
      } else {
        TRY_RESULT(h, util::get_header(std::move(line)));
        TRY_STATUS(request->add_header(std::move(h)));
      }
    }
  }
  return std::move(request);
}
HttpRequest::HttpRequest(std::string method, std::string url, std::string proto_version)
    : method_(std::move(method)), url_(std::move(url)), proto_version_(std::move(proto_version)) {
  if (proto_version_ == "HTTP/1.1") {
    keep_alive_ = true;
  } else {
    keep_alive_ = false;
  }
}
td::Result> HttpRequest::create(std::string method, std::string url,
                                                             std::string proto_version) {
  if (proto_version != "HTTP/1.0" && proto_version != "HTTP/1.1") {
    return td::Status::Error(PSTRING() << "unsupported http version '" << proto_version << "'");
  }
  static const std::vector supported_methods{"GET",    "HEAD",    "POST",    "PUT",
                                                          "DELETE", "CONNECT", "OPTIONS", "TRACE"};
  bool found = false;
  for (const auto &e : supported_methods) {
    if (e == method) {
      found = true;
      break;
    }
  }
  if (!found) {
    return td::Status::Error(PSTRING() << "unsupported http method '" << method << "'");
  }
  return std::make_unique(std::move(method), std::move(url), std::move(proto_version));
}
bool HttpRequest::check_parse_header_completed() const {
  return parse_header_completed_;
}
td::Status HttpRequest::complete_parse_header() {
  CHECK(!parse_header_completed_);
  parse_header_completed_ = true;
  return td::Status::OK();
}
td::Result> HttpRequest::create_empty_payload() {
  CHECK(check_parse_header_completed());
  if (!need_payload()) {
    return std::make_shared(HttpPayload::PayloadType::pt_empty);
  } else if (found_content_length_) {
    return std::make_shared(HttpPayload::PayloadType::pt_content_length, low_watermark(), high_watermark(),
                                         content_length_);
  } else if (found_transfer_encoding_) {
    return std::make_shared(HttpPayload::PayloadType::pt_chunked, low_watermark(), high_watermark());
  } else {
    return td::Status::Error("expected Content-Length/Transfer-Encoding header");
  }
}
bool HttpRequest::need_payload() const {
  return found_content_length_ || found_transfer_encoding_;
}
td::Status HttpRequest::add_header(HttpHeader header) {
  auto lc_name = header.name;
  auto lc_value = header.value;
  std::transform(lc_name.begin(), lc_name.end(), lc_name.begin(), [](unsigned char c) { return std::tolower(c); });
  std::transform(lc_value.begin(), lc_value.end(), lc_value.begin(), [](unsigned char c) { return std::tolower(c); });
  auto S = td::trim(td::Slice(lc_value));
  if (lc_name == "content-length") {
    TRY_RESULT(len, td::to_integer_safe(S));
    if (found_transfer_encoding_ || found_content_length_) {
      return td::Status::Error("duplicate Content-Length/Transfer-Encoding");
    }
    if (len > HttpRequest::max_payload_size()) {
      return td::Status::Error("too big Content-Length");
    }
    content_length_ = len;
    found_content_length_ = true;
  } else if (lc_name == "transfer-encoding") {
    // expect chunked, don't event check
    if (found_transfer_encoding_ || found_content_length_) {
      return td::Status::Error("duplicate Content-Length/Transfer-Encoding");
    }
    found_transfer_encoding_ = true;
  } else if (lc_name == "host") {
    if (host_.size() > 0) {
      return td::Status::Error("duplicate Host");
    }
    host_ = S.str();
  } else if (lc_name == "connection" && S == "keep-alive") {
    keep_alive_ = true;
    return td::Status::OK();
  } else if (lc_name == "connection" && S == "close") {
    keep_alive_ = false;
    return td::Status::OK();
  } else if (lc_name == "proxy-connection" && S == "keep-alive") {
    keep_alive_ = true;
    return td::Status::OK();
  } else if (lc_name == "proxy-connection" && S == "close") {
    keep_alive_ = false;
    return td::Status::OK();
  }
  options_.emplace_back(std::move(header));
  return td::Status::OK();
}
void HttpRequest::store_http(td::ChainBufferWriter &output) {
  std::string line = method_ + " " + url_ + " " + proto_version_ + "\r\n";
  output.append(line);
  for (auto &x : options_) {
    x.store_http(output);
  }
  if (keep_alive_) {
    HttpHeader{"Connection", "Keep-Alive"}.store_http(output);
  } else {
    HttpHeader{"Connection", "Close"}.store_http(output);
  }
  output.append(td::Slice("\r\n", 2));
}
tl_object_ptr HttpRequest::store_tl(td::Bits256 req_id) {
  std::vector> headers;
  headers.reserve(options_.size());
  for (auto &h : options_) {
    headers.push_back(h.store_tl());
  }
  if (keep_alive_) {
    headers.push_back(HttpHeader{"Connection", "Keep-Alive"}.store_tl());
  } else {
    headers.push_back(HttpHeader{"Connection", "Close"}.store_tl());
  }
  return create_tl_object(req_id, method_, url_, proto_version_, std::move(headers));
}
td::Status HttpPayload::parse(td::ChainBufferReader &input) {
  CHECK(!parse_completed());
  while (true) {
    if (high_watermark_reached()) {
      return td::Status::OK();
    }
    switch (state_) {
      case ParseState::reading_chunk_header: {
        bool read;
        TRY_RESULT(l, util::get_line(input, tmp_, read, HttpRequest::max_one_header_size()));
        if (!read) {
          return td::Status::OK();
        }
        if (l.size() == 0) {
          return td::Status::Error("expected chunk, found empty line");
        }
        auto v = td::split(l);
        TRY_RESULT(size, td::hex_to_integer_safe(v.first));
        if (size == 0) {
          state_ = ParseState::reading_trailer;
          break;
        }
        cur_chunk_size_ = size;
        state_ = ParseState::reading_chunk_data;
      } break;
      case ParseState::reading_chunk_data: {
        if (cur_chunk_size_ == 0) {
          switch (type_) {
            case PayloadType::pt_empty:
              UNREACHABLE();
            case PayloadType::pt_eof:
              cur_chunk_size_ = 1 << 30;
              break;
            case PayloadType::pt_chunked:
              state_ = ParseState::reading_crlf;
              break;
            case PayloadType::pt_content_length: {
              LOG(INFO) << "payload parse success";
              const std::lock_guard lock{mutex_};
              state_ = ParseState::completed;
              run_callbacks();
              return td::Status::OK();
            } break;
          }
          break;
        }
        if (input.size() == 0) {
          return td::Status::OK();
        }
        auto S = get_read_slice();
        auto s = input.size();
        if (S.size() > s) {
          S.truncate(s);
        }
        CHECK(input.advance(S.size(), S) == S.size());
        confirm_read(S.size());
      } break;
      case ParseState::reading_trailer: {
        bool read;
        TRY_RESULT(l, util::get_line(input, tmp_, read, HttpRequest::max_one_header_size()));
        if (!read) {
          return td::Status::OK();
        }
        if (!l.size()) {
          LOG(INFO) << "payload parse success";
          const std::lock_guard lock{mutex_};
          state_ = ParseState::completed;
          run_callbacks();
          return td::Status::OK();
        }
        TRY_RESULT(h, util::get_header(std::move(l)));
        add_trailer(std::move(h));
        if (trailer_size_ > HttpRequest::max_header_size()) {
          return td::Status::Error("too big trailer part");
        }
      } break;
      case ParseState::reading_crlf: {
        if (input.size() < 2) {
          return td::Status::OK();
        }
        td::uint8 buf[2];
        CHECK(input.advance(2, td::MutableSlice(buf, 2)) == 2);
        if (buf[0] != '\r' || buf[1] != '\n') {
          return td::Status::Error(PSTRING()
                                   << "expected CRLF " << static_cast(buf[0]) << " " << static_cast(buf[1]));
        }
        state_ = ParseState::reading_chunk_header;
      } break;
      case ParseState::completed:
        return td::Status::OK();
    }
  }
}
bool HttpPayload::parse_completed() const {
  return state_.load(std::memory_order_consume) == ParseState::completed;
}
td::MutableSlice HttpPayload::get_read_slice() {
  const std::lock_guard lock{mutex_};
  if (last_chunk_free_ == 0) {
    auto B = td::BufferSlice{chunk_size_};
    last_chunk_free_ = B.size();
    chunks_.push_back(std::move(B));
  }
  auto b = chunks_.back().as_slice();
  b.remove_prefix(b.size() - last_chunk_free_);
  if (b.size() > cur_chunk_size_) {
    b.truncate(cur_chunk_size_);
  }
  return b;
}
void HttpPayload::confirm_read(size_t s) {
  const std::lock_guard lock{mutex_};
  last_chunk_free_ -= s;
  cur_chunk_size_ -= s;
  ready_bytes_ += s;
  run_callbacks();
}
void HttpPayload::add_trailer(HttpHeader header) {
  const std::lock_guard lock{mutex_};
  ready_bytes_ += header.size();
  trailer_size_ += header.size();
  run_callbacks();
  trailer_.push_back(std::move(header));
}
void HttpPayload::add_chunk(td::BufferSlice data) {
  //LOG(INFO) << "payload: added " << data.size() << " bytes";
  while (data.size() > 0) {
    if (!cur_chunk_size_) {
      cur_chunk_size_ = data.size();
    }
    auto S = get_read_slice();
    CHECK(S.size() > 0);
    if (S.size() > data.size()) {
      S.truncate(data.size());
    }
    S.copy_from(data.as_slice().truncate(S.size()));
    data.confirm_read(S.size());
    confirm_read(S.size());
  }
}
void HttpPayload::slice_gc() {
  const std::lock_guard lock{mutex_};
  while (chunks_.size() > 0) {
    auto &x = chunks_.front();
    if (state_ == ParseState::completed || state_ == ParseState::reading_trailer) {
      if (chunks_.size() == 1) {
        x.truncate(x.size() - last_chunk_free_);
        last_chunk_free_ = 0;
      }
    }
    if (x.size() == 0) {
      CHECK(chunks_.size() > 1 || !last_chunk_free_);
      chunks_.pop_front();
      continue;
    }
    break;
  }
}
td::BufferSlice HttpPayload::get_slice(size_t max_size) {
  const std::lock_guard lock{mutex_};
  while (chunks_.size() > 0) {
    auto &x = chunks_.front();
    if (x.size() == 0) {
      CHECK(chunks_.size() > 1 || !last_chunk_free_);
      chunks_.pop_front();
      continue;
    }
    td::BufferSlice b;
    if (chunks_.size() > 1 || !last_chunk_free_) {
      if (x.size() <= max_size) {
        b = std::move(x);
        chunks_.pop_front();
      } else {
        b = x.clone();
        b.truncate(max_size);
        x.confirm_read(max_size);
      }
    } else {
      b = x.clone();
      CHECK(b.size() >= last_chunk_free_);
      if (b.size() == last_chunk_free_) {
        return td::BufferSlice{};
      }
      b.truncate(b.size() - last_chunk_free_);
      if (b.size() > max_size) {
        b.truncate(max_size);
      }
      x.confirm_read(b.size());
    }
    ready_bytes_ -= b.size();
    run_callbacks();
    return b;
  }
  return td::BufferSlice{};
}
HttpHeader HttpPayload::get_header() {
  const std::lock_guard lock{mutex_};
  if (trailer_.size() == 0) {
    return HttpHeader{};
  } else {
    auto h = std::move(trailer_.front());
    auto s = h.size();
    trailer_.pop_front();
    ready_bytes_ -= s;
    run_callbacks();
    return h;
  }
}
void HttpPayload::run_callbacks() {
  for (auto &x : callbacks_) {
    if (state_.load(std::memory_order_relaxed) == ParseState::completed) {
      x->completed();
    } else {
      x->run(ready_bytes_);
    }
  }
}
void HttpPayload::store_http(td::ChainBufferWriter &output, size_t max_size, HttpPayload::PayloadType store_type) {
  if (store_type == PayloadType::pt_empty) {
    return;
  }
  slice_gc();
  while (chunks_.size() > 0 && max_size > 0) {
    auto cur_state = state_.load(std::memory_order_consume);
    auto s = get_slice(max_size);
    if (s.size() == 0) {
      if (cur_state != ParseState::reading_trailer && cur_state != ParseState::completed) {
        return;
      } else {
        break;
      }
    }
    CHECK(s.size() <= max_size);
    max_size -= s.size();
    if (store_type == PayloadType::pt_chunked) {
      char buf[64];
      ::sprintf(buf, "%lx\r\n", s.size());
      output.append(td::Slice(buf, strlen(buf)));
    }
    output.append(std::move(s));
    if (store_type == PayloadType::pt_chunked) {
      output.append(td::Slice("\r\n", 2));
    }
  }
  if (chunks_.size() != 0) {
    return;
  }
  if (!written_zero_chunk_) {
    if (store_type == PayloadType::pt_chunked) {
      output.append(td::Slice("0\r\n", 3));
    }
    written_zero_chunk_ = true;
  }
  if (store_type != PayloadType::pt_chunked) {
    written_trailer_ = true;
    return;
  }
  while (max_size > 0) {
    auto cur_state = state_.load(std::memory_order_consume);
    HttpHeader h = get_header();
    if (h.empty()) {
      if (cur_state != ParseState::completed) {
        return;
      } else {
        break;
      }
    }
    auto s = h.size();
    h.store_http(output);
    if (max_size <= s) {
      return;
    }
    max_size -= s;
  }
  if (!written_trailer_) {
    output.append(td::Slice("\r\n", 2));
    written_trailer_ = true;
  }
}
tl_object_ptr HttpPayload::store_tl(size_t max_size) {
  auto b = ready_bytes();
  if (b > max_size) {
    b = max_size;
  }
  max_size = b;
  td::BufferSlice x{b};
  auto S = x.as_slice();
  auto obj = create_tl_object(std::move(x),
                                                         std::vector>(), false);
  slice_gc();
  while (chunks_.size() > 0 && max_size > 0) {
    auto cur_state = state_.load(std::memory_order_consume);
    auto s = get_slice(max_size);
    if (s.size() == 0) {
      if (cur_state != ParseState::reading_trailer && cur_state != ParseState::completed) {
        LOG(INFO) << "state not trailer/completed";
        obj->data_.truncate(obj->data_.size() - S.size());
        return obj;
      } else {
        break;
      }
    }
    CHECK(s.size() <= max_size);
    S.copy_from(s);
    S.remove_prefix(s.size());
    max_size -= s.size();
  }
  obj->data_.truncate(obj->data_.size() - S.size());
  if (chunks_.size() != 0) {
    return obj;
  }
  if (!written_zero_chunk_) {
    written_zero_chunk_ = true;
  }
  LOG(INFO) << "data completed";
  while (max_size > 0) {
    auto cur_state = state_.load(std::memory_order_consume);
    HttpHeader h = get_header();
    if (h.empty()) {
      if (cur_state != ParseState::completed) {
        LOG(INFO) << "state not completed";
        return obj;
      } else {
        break;
      }
    }
    auto s = h.size();
    obj->trailer_.push_back(h.store_tl());
    if (max_size <= s) {
      return obj;
    }
    max_size -= s;
  }
  written_trailer_ = true;
  obj->last_ = true;
  return obj;
}
/*tl_object_ptr HttpPayload::store_tl(size_t max_size) {
  auto obj = create_tl_object(std::vector(),
                                                         std::vector>(), false);
  if (type_ == PayloadType::pt_empty) {
    return obj;
  }
  size_t sum = 0;
  while (chunks_.size() > 0) {
    auto &p = chunks_.front();
    size_t s = p.size();
    bool m = true;
    if (chunks_.size() == 1) {
      s -= last_chunk_free_;
      m = false;
    }
    sum += s;
    if (m) {
      obj->data_.push_back(std::move(p));
    } else {
      auto B = p.clone();
      B.truncate(s);
      obj->data_.push_back(std::move(B));
      p.confirm_read(s);
    }
    CHECK(ready_bytes_ >= s);
    ready_bytes_ -= s;
    if (!m) {
      return obj;
    }
    chunks_.pop_front();
    if (sum > max_size) {
      return obj;
    }
  }
  if (state_ != ParseState::reading_trailer && state_ != ParseState::completed) {
    return obj;
  }
  if (!written_zero_chunk_) {
    written_zero_chunk_ = true;
  }
  while (true) {
    if (trailer_.size() == 0) {
      break;
    }
    auto &p = trailer_.front();
    sum += p.name.size() + p.value.size() + 2;
    ready_bytes_ -= p.name.size() + p.value.size() + 2;
    obj->trailer_.push_back(p.store_tl());
    trailer_.pop_front();
    if (sum > max_size) {
      return obj;
    }
  }
  if (state_ != ParseState::completed) {
    return obj;
  }
  obj->last_ = true;
  return obj;
}
tl_object_ptr HttpPayload::store_info(size_t max_size) {
  if (type_ == PayloadType::pt_empty) {
    return create_tl_object(create_tl_object());
  }
  if (!parse_completed()) {
    return create_tl_object();
  }
  if (ready_bytes_ > max_size) {
    return create_tl_object(ready_bytes_);
  }
  auto obj = store_tl(max_size);
  CHECK(obj->last_);
  return create_tl_object(
      create_tl_object(std::move(obj->data_), std::move(obj->trailer_)));
}*/
void HttpPayload::add_callback(std::unique_ptr callback) {
  const std::lock_guard lock{mutex_};
  callbacks_.push_back(std::move(callback));
}
td::Result> HttpResponse::parse(std::unique_ptr response,
                                                              std::string &cur_line, bool force_no_payload,
                                                              bool keep_alive, bool &exit_loop,
                                                              td::ChainBufferReader &input) {
  exit_loop = false;
  CHECK(!response || !response->check_parse_header_completed());
  while (true) {
    bool read;
    TRY_RESULT(line, util::get_line(input, cur_line, read, HttpRequest::max_one_header_size()));
    if (!read) {
      exit_loop = true;
      break;
    }
    if (!response) {
      auto v = td::full_split(line, ' ', 3);
      if (v.size() != 3) {
        return td::Status::Error("expected http header in form ");
      }
      TRY_RESULT(code, td::to_integer_safe(std::move(v[1])));
      TRY_RESULT_ASSIGN(response, HttpResponse::create(v[0], code, v[2], force_no_payload, keep_alive));
    } else {
      if (line.size() == 0) {
        TRY_STATUS(response->complete_parse_header());
        break;
      } else {
        TRY_RESULT(h, util::get_header(std::move(line)));
        TRY_STATUS(response->add_header(std::move(h)));
      }
    }
  }
  return std::move(response);
}
HttpResponse::HttpResponse(std::string proto_version, td::uint32 code, std::string reason, bool force_no_payload,
                           bool keep_alive)
    : proto_version_(std::move(proto_version))
    , code_(code)
    , reason_(std::move(reason))
    , force_no_payload_(force_no_payload)
    , force_no_keep_alive_(!keep_alive) {
}
td::Result> HttpResponse::create(std::string proto_version, td::uint32 code,
                                                               std::string reason, bool force_no_payload,
                                                               bool keep_alive) {
  if (proto_version != "HTTP/1.0" && proto_version != "HTTP/1.1") {
    return td::Status::Error(PSTRING() << "unsupported http version '" << proto_version << "'");
  }
  if (code < 100 || code > 999) {
    return td::Status::Error(PSTRING() << "bad status code '" << code << "'");
  }
  return std::make_unique(std::move(proto_version), code, std::move(reason), force_no_payload,
                                        keep_alive);
}
td::Status HttpResponse::complete_parse_header() {
  CHECK(!parse_header_completed_);
  parse_header_completed_ = true;
  return td::Status::OK();
}
bool HttpResponse::check_parse_header_completed() const {
  return parse_header_completed_;
}
td::Result> HttpResponse::create_empty_payload() {
  CHECK(check_parse_header_completed());
  if (!need_payload()) {
    return std::make_shared(HttpPayload::PayloadType::pt_empty);
  } else if (found_content_length_) {
    return std::make_shared(HttpPayload::PayloadType::pt_content_length, low_watermark(), high_watermark(),
                                         content_length_);
  } else if (found_transfer_encoding_) {
    return std::make_shared(HttpPayload::PayloadType::pt_chunked, low_watermark(), high_watermark());
  } else {
    return std::make_shared(HttpPayload::PayloadType::pt_eof, low_watermark(), high_watermark());
  }
}
bool HttpResponse::need_payload() const {
  return !force_no_payload_ && (code_ >= 200) && code_ != 204 && code_ != 304;
}
td::Status HttpResponse::add_header(HttpHeader header) {
  auto lc_name = header.name;
  auto lc_value = header.value;
  std::transform(lc_name.begin(), lc_name.end(), lc_name.begin(), [](unsigned char c) { return std::tolower(c); });
  std::transform(lc_value.begin(), lc_value.end(), lc_value.begin(), [](unsigned char c) { return std::tolower(c); });
  auto S = td::trim(td::Slice(lc_value));
  if (lc_name == "content-length") {
    TRY_RESULT(len, td::to_integer_safe(S));
    if (found_transfer_encoding_ || found_content_length_) {
      return td::Status::Error("duplicate Content-Length/Transfer-Encoding");
    }
    if (len > HttpRequest::max_payload_size()) {
      return td::Status::Error("too big Content-Length");
    }
    content_length_ = len;
    found_content_length_ = true;
  } else if (lc_name == "transfer-encoding") {
    // expect chunked, don't event check
    if (found_transfer_encoding_ || found_content_length_) {
      return td::Status::Error("duplicate Content-Length/Transfer-Encoding");
    }
    found_transfer_encoding_ = true;
  } else if (lc_name == "connection" && S == "keep-alive") {
    keep_alive_ = true;
    return td::Status::OK();
  } else if (lc_name == "connection" && S == "close") {
    keep_alive_ = false;
    return td::Status::OK();
  } else if (lc_name == "proxy-connection" && S == "keep-alive") {
    keep_alive_ = true;
    return td::Status::OK();
  } else if (lc_name == "proxy-connection" && S == "close") {
    keep_alive_ = false;
    return td::Status::OK();
  }
  options_.emplace_back(std::move(header));
  return td::Status::OK();
}
void HttpResponse::store_http(td::ChainBufferWriter &output) {
  std::string line = proto_version_ + " " + std::to_string(code_) + " " + reason_ + "\r\n";
  output.append(line);
  for (auto &x : options_) {
    x.store_http(output);
  }
  if (keep_alive_) {
    HttpHeader{"Connection", "Keep-Alive"}.store_http(output);
  } else {
    HttpHeader{"Connection", "Close"}.store_http(output);
  }
  output.append(td::Slice("\r\n", 2));
}
tl_object_ptr HttpResponse::store_tl() {
  std::vector> headers;
  headers.reserve(options_.size());
  for (auto &h : options_) {
    headers.push_back(h.store_tl());
  }
  if (keep_alive_) {
    headers.push_back(HttpHeader{"Connection", "Keep-Alive"}.store_tl());
  } else {
    headers.push_back(HttpHeader{"Connection", "Close"}.store_tl());
  }
  return create_tl_object(proto_version_, code_, reason_, std::move(headers));
}
td::Status HttpHeader::basic_check() {
  for (auto &c : name) {
    if (c == ' ' || c == '\t' || c == '\r' || c == '\n' || c == ':') {
      return td::Status::Error("bad character in header name");
    }
  }
  for (auto &c : value) {
    if (c == '\r' || c == '\n') {
      return td::Status::Error("bad character in header name");
    }
  }
  return td::Status::OK();
}
void answer_error(HttpStatusCode code, std::string reason,
                  td::Promise, std::shared_ptr>> promise) {
  if (reason.empty()) {
    switch (code) {
      case status_ok:
        reason = "OK";
        break;
      case status_bad_request:
        reason = "Bad Request";
        break;
      case status_method_not_allowed:
        reason = "Method Not Allowed";
        break;
      case status_internal_server_error:
        reason = "Internal Server Error";
        break;
      case status_bad_gateway:
        reason = "Bad Gateway";
        break;
      case status_gateway_timeout:
        reason = "Gateway Timeout";
        break;
      default:
        reason = "Unknown";
        break;
    }
  }
  auto response = HttpResponse::create("HTTP/1.0", code, reason, false, false).move_as_ok();
  response->add_header(HttpHeader{"Content-Length", "0"});
  auto payload = response->create_empty_payload().move_as_ok();
  CHECK(payload->parse_completed());
  promise.set_value(std::make_pair(std::move(response), std::move(payload)));
}
}  // namespace http
}  // namespace ton