text: //
  // Copyright (c) 2016-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
  //
  // Distributed under the Boost Software License, Version 1.0. (See accompanying
  // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
  //
  // Official repository: https://github.com/boostorg/beast
  //
  
  #ifndef BOOST_BEAST_HTTP_NODEJS_PARSER_HPP
  #define BOOST_BEAST_HTTP_NODEJS_PARSER_HPP
  
  #include "nodejs-parser/http_parser.h"
  
  #include <boost/beast/http/message.hpp>
  #include <boost/beast/http/rfc7230.hpp>
  #include <boost/beast/core/error.hpp>
  #include <boost/beast/core/type_traits.hpp>
  #include <boost/asio/buffer.hpp>
  #include <boost/system/error_code.hpp>
  #include <boost/throw_exception.hpp>
  #include <cstdint>
  #include <string>
  #include <type_traits>
  #include <utility>
  
  namespace boost {
  namespace beast {
  namespace http {
  
  namespace detail {
  
  class nodejs_message_category
      : public boost::system::error_category
  {
  public:
      const char*
      name() const noexcept override
      {
          return "nodejs-http-error";
      }
  
      std::string
      message(int ev) const override
      {
          return http_errno_description(
              static_cast<http_errno>(ev));
      }
  
      boost::system::error_condition
      default_error_condition(int ev) const noexcept override
      {
          return boost::system::error_condition{ev, *this};
      }
  
      bool
      equivalent(int ev,
          boost::system::error_condition const& condition
              ) const noexcept override
      {
          return condition.value() == ev &&
              &condition.category() == this;
      }
  
      bool
      equivalent(boost::system::error_code const& error,
          int ev) const noexcept override
      {
          return error.value() == ev &&
              &error.category() == this;
      }
  };
  
  template<class = void>
  boost::system::error_code
  make_nodejs_error(int http_errno)
  {
      static nodejs_message_category const mc{};
      return boost::system::error_code{http_errno, mc};
  }
  
  inline
  char const*
  method_to_string(unsigned method)
  {
      using namespace beast;
      switch(static_cast<http_method>(method))
      {
      case HTTP_DELETE:      return "DELETE";
      case HTTP_GET:         return "GET";
      case HTTP_HEAD:        return "HEAD";
      case HTTP_POST:        return "POST";
      case HTTP_PUT:         return "PUT";
  
      // pathological
      case HTTP_CONNECT:     return "CONNECT";
      case HTTP_OPTIONS:     return "OPTIONS";
      case HTTP_TRACE:       return "TRACE";
  
      // webdav
      case HTTP_COPY:        return "COPY";
      case HTTP_LOCK:        return "LOCK";
      case HTTP_MKCOL:       return "MKCOL";
      case HTTP_MOVE:        return "MOVE";
      case HTTP_PROPFIND:    return "PROPFIND";
      case HTTP_PROPPATCH:   return "PROPPATCH";
      case HTTP_SEARCH:      return "SEARCH";
      case HTTP_UNLOCK:      return "UNLOCK";
      case HTTP_BIND:        return "BIND";
      case HTTP_REBIND:      return "REBIND";
      case HTTP_UNBIND:      return "UNBIND";
      case HTTP_ACL:         return "ACL";
  
      // subversion
      case HTTP_REPORT:      return "REPORT";
      case HTTP_MKACTIVITY:  return "MKACTIVITY";
      case HTTP_CHECKOUT:    return "CHECKOUT";
      case HTTP_MERGE:       return "MERGE";
  
      // upnp
      case HTTP_MSEARCH:     return "MSEARCH";
      case HTTP_NOTIFY:      return "NOTIFY";
      case HTTP_SUBSCRIBE:   return "SUBSCRIBE";
      case HTTP_UNSUBSCRIBE: return "UNSUBSCRIBE";
  
      // RFC-5789
      case HTTP_PATCH:       return "PATCH";
      case HTTP_PURGE:       return "PURGE";
  
      // CalDav
      case HTTP_MKCALENDAR:  return "MKCALENDAR";
  
      // RFC-2068, section 19.6.1.2
      case HTTP_LINK:        return "LINK";
      case HTTP_UNLINK:      return "UNLINK";
      };
  
      return "<unknown>";
  }
  
  } // detail
  
  template<class Derived>
  class nodejs_basic_parser
  {
      http_parser state_;
      boost::system::error_code* ec_;
      bool complete_ = false;
      std::string url_;
      std::string status_;
      std::string field_;
      std::string value_;
  
  public:
      using error_code = boost::system::error_code;
  
      nodejs_basic_parser(nodejs_basic_parser&& other);
  
      nodejs_basic_parser&
      operator=(nodejs_basic_parser&& other);
  
      nodejs_basic_parser(nodejs_basic_parser const& other);
  
      nodejs_basic_parser& operator=(nodejs_basic_parser const& other);
  
      explicit
      nodejs_basic_parser(bool request) noexcept;
  
      bool
      complete() const noexcept
      {
          return complete_;
      }
  
      std::size_t
      write(void const* data, std::size_t size)
      {
          error_code ec;
          auto const used = write(data, size, ec);
          if(ec)
              BOOST_THROW_EXCEPTION(system_error{ec});
          return used;
      }
  
      std::size_t
      write(void const* data, std::size_t size,
          error_code& ec);
  
      template<class ConstBufferSequence>
      std::size_t
      write(ConstBufferSequence const& buffers)
      {
          error_code ec;
          auto const used = write(buffers, ec);
          if(ec)
              BOOST_THROW_EXCEPTION(system_error{ec});
          return used;
      }
  
      template<class ConstBufferSequence>
      std::size_t
      write(ConstBufferSequence const& buffers,
          error_code& ec);
  
      void
      write_eof()
      {
          error_code ec;
          write_eof(ec);
          if(ec)
              BOOST_THROW_EXCEPTION(system_error{ec});
      }
  
      void
      write_eof(error_code& ec);
  
      void
      check_header();
  
  private:
      Derived&
      impl()
      {
          return *static_cast<Derived*>(this);
      }
  
      static int cb_message_start(http_parser*);
      static int cb_url(http_parser*, char const*, std::size_t);
      static int cb_status(http_parser*, char const*, std::size_t);
      static int cb_header_field(http_parser*, char const*, std::size_t);
      static int cb_header_value(http_parser*, char const*, std::size_t);
      static int cb_headers_complete(http_parser*);
      static int cb_body(http_parser*, char const*, std::size_t);
      static int cb_message_complete(http_parser*);
      static int cb_chunk_header(http_parser*);
      static int cb_chunk_complete(http_parser*);
  
      struct hooks_t : http_parser_settings
      {
          hooks_t()
          {
              http_parser_settings_init(this);
              on_message_begin    = &nodejs_basic_parser::cb_message_start;
              on_url              = &nodejs_basic_parser::cb_url;
              on_status           = &nodejs_basic_parser::cb_status;
              on_header_field     = &nodejs_basic_parser::cb_header_field;
              on_header_value     = &nodejs_basic_parser::cb_header_value;
              on_headers_complete = &nodejs_basic_parser::cb_headers_complete;
              on_body             = &nodejs_basic_parser::cb_body;
              on_message_complete = &nodejs_basic_parser::cb_message_complete;
              on_chunk_header     = &nodejs_basic_parser::cb_chunk_header;
              on_chunk_complete   = &nodejs_basic_parser::cb_chunk_complete;
          }
      };
  
      static
      http_parser_settings const*
      hooks();
  };
  
  template<class Derived>
  template<class ConstBufferSequence>
  std::size_t
  nodejs_basic_parser<Derived>::write(
      ConstBufferSequence const& buffers, error_code& ec)
  {
      static_assert(boost::asio::is_const_buffer_sequence<
          ConstBufferSequence>::value,
              "ConstBufferSequence requirements not met");
      std::size_t bytes_used = 0;
      for(auto buffer : beast::detail::buffers_range(buffers))
      {
          auto const n = write(
              static_cast<void const*>(buffer.data()),
                  buffer.size(), ec);
          if(ec)
              return 0;
          bytes_used += n;
          if(complete())
              break;
      }
      return bytes_used;
  }
  
  template<class Derived>
  http_parser_settings const*
  nodejs_basic_parser<Derived>::hooks()
  {
      static hooks_t const h;
      return &h;
  }
  
  template<class Derived>
  nodejs_basic_parser<Derived>::
  nodejs_basic_parser(nodejs_basic_parser&& other)
  {
      state_ = other.state_;
      state_.data = this;
      complete_ = other.complete_;
      url_ = std::move(other.url_);
      status_ = std::move(other.status_);
      field_ = std::move(other.field_);
      value_ = std::move(other.value_);
  }
  
  template<class Derived>
  auto
  nodejs_basic_parser<Derived>::
  operator=(nodejs_basic_parser&& other) ->
      nodejs_basic_parser&
  {
      state_ = other.state_;
      state_.data = this;
      complete_ = other.complete_;
      url_ = std::move(other.url_);
      status_ = std::move(other.status_);
      field_ = std::move(other.field_);
      value_ = std::move(other.value_);
      return *this;
  }
  
  template<class Derived>
  nodejs_basic_parser<Derived>::
  nodejs_basic_parser(nodejs_basic_parser const& other)
  {
      state_ = other.state_;
      state_.data = this;
      complete_ = other.complete_;
      url_ = other.url_;
      status_ = other.status_;
      field_ = other.field_;
      value_ = other.value_;
  }
  
  template<class Derived>
  auto
  nodejs_basic_parser<Derived>::
  operator=(nodejs_basic_parser const& other) ->
      nodejs_basic_parser&
  {
      state_ = other.state_;
      state_.data = this;
      complete_ = other.complete_;
      url_ = other.url_;
      status_ = other.status_;
      field_ = other.field_;
      value_ = other.value_;
      return *this;
  }
  
  template<class Derived>
  nodejs_basic_parser<Derived>::
  nodejs_basic_parser(bool request) noexcept
  {
      state_.data = this;
      http_parser_init(&state_, request
          ? http_parser_type::HTTP_REQUEST
          : http_parser_type::HTTP_RESPONSE);
  }
  
  template<class Derived>
  std::size_t
  nodejs_basic_parser<Derived>::
  write(void const* data,
      std::size_t size, error_code& ec)
  {
      ec_ = &ec;
      auto const n = http_parser_execute(
          &state_, hooks(),
              static_cast<const char*>(data), size);
      if(! ec)
          ec = detail::make_nodejs_error(
              static_cast<int>(state_.http_errno));
      if(ec)
          return 0;
      return n;
  }
  
  template<class Derived>
  void
  nodejs_basic_parser<Derived>::
  write_eof(error_code& ec)
  {
      ec_ = &ec;
      http_parser_execute(&state_, hooks(), nullptr, 0);
      if(! ec)
          ec = detail::make_nodejs_error(
              static_cast<int>(state_.http_errno));
  }
  
  template<class Derived>
  void
  nodejs_basic_parser<Derived>::
  check_header()
  {
      if(! value_.empty())
      {
          impl().on_field(field_, value_);
          field_.clear();
          value_.clear();
      }
  }
  
  template<class Derived>
  int
  nodejs_basic_parser<Derived>::
  cb_message_start(http_parser* p)
  {
      auto& t = *reinterpret_cast<nodejs_basic_parser*>(p->data);
      t.complete_ = false;
      t.url_.clear();
      t.status_.clear();
      t.field_.clear();
      t.value_.clear();
      t.impl().on_start();
      return 0;
  }
  
  template<class Derived>
  int
  nodejs_basic_parser<Derived>::
  cb_url(http_parser* p,
      char const* in, std::size_t bytes)
  {
      auto& t = *reinterpret_cast<nodejs_basic_parser*>(p->data);
      t.url_.append(in, bytes);
      return 0;
  }
  
  template<class Derived>
  int
  nodejs_basic_parser<Derived>::
  cb_status(http_parser* p,
      char const* in, std::size_t bytes)
  {
      auto& t = *reinterpret_cast<nodejs_basic_parser*>(p->data);
      t.status_.append(in, bytes);
      return 0;
  }
  
  template<class Derived>
  int
  nodejs_basic_parser<Derived>::
  cb_header_field(http_parser* p,
      char const* in, std::size_t bytes)
  {
      auto& t = *reinterpret_cast<nodejs_basic_parser*>(p->data);
      t.check_header();
      t.field_.append(in, bytes);
      return 0;
  }
  
  template<class Derived>
  int
  nodejs_basic_parser<Derived>::
  cb_header_value(http_parser* p,
      char const* in, std::size_t bytes)
  {
      auto& t = *reinterpret_cast<nodejs_basic_parser*>(p->data);
      t.value_.append(in, bytes);
      return 0;
  }
  
  template<class Derived>
  int
  nodejs_basic_parser<Derived>::
  cb_headers_complete(http_parser* p)
  {
      auto& t = *reinterpret_cast<nodejs_basic_parser*>(p->data);
      t.check_header();
      t.impl().on_headers_complete(*t.ec_);
      if(*t.ec_)
          return 1;
      bool const keep_alive =
          http_should_keep_alive(p) != 0;
      if(p->type == http_parser_type::HTTP_REQUEST)
      {
          t.impl().on_request(p->method, t.url_,
              p->http_major, p->http_minor, keep_alive,
                  p->upgrade);
          return 0;
      }
      return t.impl().on_response(p->status_code, t.status_,
          p->http_major, p->http_minor, keep_alive,
              p->upgrade) ? 0 : 1;
  }
  
  template<class Derived>
  int
  nodejs_basic_parser<Derived>::
  cb_body(http_parser* p,
      char const* in, std::size_t bytes)
  {
      auto& t = *reinterpret_cast<nodejs_basic_parser*>(p->data);
      t.impl().on_body(in, bytes, *t.ec_);
      return *t.ec_ ? 1 : 0;
  }
  
  template<class Derived>
  int
  nodejs_basic_parser<Derived>::
  cb_message_complete(http_parser* p)
  {
      auto& t = *reinterpret_cast<nodejs_basic_parser*>(p->data);
      t.complete_ = true;
      t.impl().on_complete();
      return 0;
  }
  
  template<class Derived>
  int
  nodejs_basic_parser<Derived>::
  cb_chunk_header(http_parser*)
  {
      return 0;
  }
  
  template<class Derived>
  int
  nodejs_basic_parser<Derived>::
  cb_chunk_complete(http_parser*)
  {
      return 0;
  }
  
  //------------------------------------------------------------------------------
  
  /** A HTTP parser.
  
      The parser may only be used once.
  */
  template<bool isRequest, class Body, class Fields>
  class nodejs_parser
      : public nodejs_basic_parser<nodejs_parser<isRequest, Body, Fields>>
  {
      bool started_ = false;
  
  public:
      nodejs_parser(nodejs_parser&&) = default;
  
      nodejs_parser()
          : http::nodejs_basic_parser<nodejs_parser>(isRequest)
      {
      }
  
      /// Returns `true` if at least one byte has been processed
      bool
      started()
      {
          return started_;
      }
  
  private:
      friend class http::nodejs_basic_parser<nodejs_parser>;
  
      void
      on_start()
      {
          started_ = true;
      }
  
      void
      on_field(std::string const&, std::string const&)
      {
      }
  
      void
      on_headers_complete(error_code& ec)
      {
          // vFALCO TODO Decode the Content-Length and
          // Transfer-Encoding, see if we can reserve the buffer.
          //
          // r_.reserve(content_length)
          ec.assign(0, ec.category());
      }
  
      bool
      on_request(unsigned, std::string const&,
          int, int, bool, bool, std::true_type)
      {
          return true;
      }
  
      bool
      on_request(unsigned, std::string const&,
          int, int, bool, bool, std::false_type)
      {
          return true;
      }
  
      bool
      on_request(unsigned method, std::string const& url,
          int major, int minor, bool keep_alive, bool upgrade)
      {
          return on_request(method, url,
              major, minor, keep_alive, upgrade,
                  std::integral_constant<
                      bool, isRequest>{});
      }
  
      bool
      on_response(int, std::string const&,
          int, int, bool, bool, std::true_type)
      {
          return true;
      }
  
      bool
      on_response(int, std::string const&, int, int, bool, bool,
          std::false_type)
      {
          return true;
      }
  
      bool
      on_response(int status, std::string const& reason,
          int major, int minor, bool keep_alive, bool upgrade)
      {
          return on_response(
              status, reason, major, minor, keep_alive, upgrade,
                  std::integral_constant<bool, ! isRequest>{});
      }
  
      void
      on_body(void const*, std::size_t, error_code& ec)
      {
          ec.assign(0, ec.category());
      }
  
      void
      on_complete()
      {
      }
  };
  
  } // http
  } // beast
  } // boost
  
  #endif
  