[
  {
    "path": ".clang-format",
    "content": "IndentWidth: 2\nAccessModifierOffset: -2\nUseTab: Never\nColumnLimit: 0\nMaxEmptyLinesToKeep: 2\nSpaceBeforeParens: Never\nBreakBeforeBraces: Custom\nBraceWrapping: {BeforeElse: true, BeforeCatch: true}\nNamespaceIndentation: All\n"
  },
  {
    "path": ".gitignore",
    "content": "# https://github.com/github/gitignore/blob/master/CMake.gitignore\nCMakeCache.txt\nCMakeFiles\nCMakeScripts\nMakefile\ncmake_install.cmake\ninstall_manifest.txt\n*.cmake\n#Additions to https://github.com/github/gitignore/blob/master/CMake.gitignore\nTesting\ncompile_commands.json\n\n# executables\nhttp_examples\nhttps_examples\nio_test\nparse_test\ncrypto_test\n"
  },
  {
    "path": ".travis.yml",
    "content": "sudo: required\n\nservices:\n  - docker\n\nscript:\n  - sudo docker run -it -v \"$PWD:/repository\" debian:testing sh -c \"\n      apt-get update &&\n      apt-get install --yes cmake make g\\+\\+ clang perl libssl-dev libboost-thread-dev libboost-regex-dev libboost-date-time-dev libboost-filesystem-dev libasio-dev &&\n      cd /repository && mkdir build && cd build &&\n      scan-build cmake -DCMAKE_CXX_FLAGS=-Werror .. &&\n      scan-build --status-bugs make &&\n      rm -r * &&\n      CXX=clang++ cmake -DCMAKE_CXX_FLAGS=-Werror .. &&\n      make &&\n      rm -r * &&\n      CXX=g++ cmake -DCMAKE_CXX_FLAGS=-Werror .. &&\n      make &&\n      CTEST_OUTPUT_ON_FAILURE=1 make test &&\n      rm -r * &&\n      CXX=g++ cmake -DCMAKE_CXX_FLAGS=\\\"-Werror -O3 -DUSE_STANDALONE_ASIO\\\" .. &&\n      make &&\n      CTEST_OUTPUT_ON_FAILURE=1 make test\n      \"\n"
  },
  {
    "path": "CMakeLists.txt",
    "content": "cmake_minimum_required (VERSION 2.8.8)\nproject (Simple-Web-Server)\nset(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} -std=c++11 -Wall -Wextra\")\n\ninclude_directories(.)\n\nfind_package(Threads REQUIRED)\n\nset(BOOST_COMPONENTS system thread filesystem date_time)\n# Late 2017 TODO: remove the following checks and always use std::regex\nif(\"${CMAKE_CXX_COMPILER_ID}\" STREQUAL \"GNU\")\n    if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.9)\n        set(BOOST_COMPONENTS ${BOOST_COMPONENTS} regex)\n        set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} -DUSE_BOOST_REGEX\")\n    endif()\nendif()\nfind_package(Boost 1.53.0 COMPONENTS ${BOOST_COMPONENTS} REQUIRED)\ninclude_directories(SYSTEM ${Boost_INCLUDE_DIR})\n\nif(APPLE)\n    set(OPENSSL_ROOT_DIR \"/usr/local/opt/openssl\")\nendif()\n\nadd_executable(http_examples http_examples.cpp)\ntarget_link_libraries(http_examples ${Boost_LIBRARIES})\ntarget_link_libraries(http_examples ${CMAKE_THREAD_LIBS_INIT})\n\n#TODO: add requirement for version 1.0.1g (can it be done in one line?)\nfind_package(OpenSSL)\n\nif(OPENSSL_FOUND)\n    set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} -DHAVE_OPENSSL\")\n    target_link_libraries(http_examples ${OPENSSL_LIBRARIES})\n    include_directories(SYSTEM ${OPENSSL_INCLUDE_DIR})\n\n    add_executable(https_examples https_examples.cpp)\n    target_link_libraries(https_examples ${Boost_LIBRARIES})\n    target_link_libraries(https_examples ${OPENSSL_LIBRARIES})\n    target_link_libraries(https_examples ${CMAKE_THREAD_LIBS_INIT})\nendif()\n\nif(MSYS) #TODO: Is MSYS true when MSVC is true?\n    target_link_libraries(http_examples ws2_32 wsock32)\n    if(OPENSSL_FOUND)\n        target_link_libraries(https_examples ws2_32 wsock32)\n    endif()\nendif()\n\nenable_testing()\nadd_subdirectory(tests)\n\ninstall(FILES server_http.hpp client_http.hpp server_https.hpp client_https.hpp crypto.hpp utility.hpp status_code.hpp DESTINATION include/simple-web-server)\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2014-2016 Ole Christian Eidheim\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "Simple-Web-Server [![Build Status](https://travis-ci.org/eidheim/Simple-Web-Server.svg?branch=master)](https://travis-ci.org/eidheim/Simple-Web-Server)\n=================\n\nA very simple, fast, multithreaded, platform independent HTTP and HTTPS server and client library implemented using C++11 and Boost.Asio. Created to be an easy way to make REST resources available from C++ applications. \n\nSee https://github.com/eidheim/Simple-WebSocket-Server for an easy way to make WebSocket/WebSocket Secure endpoints in C++. Also, feel free to check out the new C++ IDE supporting C++11/14/17: https://github.com/cppit/jucipp. \n\n### Features\n\n* Asynchronous request handling\n* Thread pool if needed\n* Platform independent\n* HTTPS support\n* HTTP persistent connection (for HTTP/1.1)\n* Client supports chunked transfer encoding\n* Timeouts, if any of Server::timeout_request and Server::timeout_content are >0 (default: Server::timeout_request=5 seconds, and Server::timeout_content=300 seconds)\n* Simple way to add REST resources using regex for path, and anonymous functions\n\n### Usage\n\nSee http_examples.cpp or https_examples.cpp for example usage. \n\nSee particularly the JSON-POST (using Boost.PropertyTree) and the GET /match/[number] examples, which are most relevant.\n\n### Dependencies\n\n* Boost C++ libraries\n* For HTTPS: OpenSSL libraries \n\n### Compile and run\n\nCompile with a C++11 compliant compiler:\n```sh\nmkdir build\ncd build\ncmake ..\nmake\ncd ..\n```\n\n#### HTTP\n\nRun the server and client examples: `./build/http_examples`\n\nDirect your favorite browser to for instance http://localhost:8080/\n\n#### HTTPS\n\nBefore running the server, an RSA private key (server.key) and an SSL certificate (server.crt) must be created. Follow, for instance, the instructions given here (for a self-signed certificate): http://www.akadia.com/services/ssh_test_certificate.html\n\nRun the server and client examples: `./build/https_examples`\n\nDirect your favorite browser to for instance https://localhost:8080/\n\n"
  },
  {
    "path": "client_http.hpp",
    "content": "#ifndef CLIENT_HTTP_HPP\n#define CLIENT_HTTP_HPP\n\n#include \"utility.hpp\"\n#include <mutex>\n#include <random>\n#include <vector>\n\n#ifdef USE_STANDALONE_ASIO\n#include <asio.hpp>\nnamespace SimpleWeb {\n  using error_code = std::error_code;\n  using errc = std::errc;\n  using system_error = std::system_error;\n  namespace make_error_code = std;\n  using string_view = const std::string &; // TODO c++17: use std::string_view\n} // namespace SimpleWeb\n#else\n#include <boost/asio.hpp>\n#include <boost/utility/string_ref.hpp>\nnamespace SimpleWeb {\n  namespace asio = boost::asio;\n  using error_code = boost::system::error_code;\n  namespace errc = boost::system::errc;\n  using system_error = boost::system::system_error;\n  namespace make_error_code = boost::system::errc;\n  using string_view = boost::string_ref;\n} // namespace SimpleWeb\n#endif\n\nnamespace SimpleWeb {\n  template <class socket_type>\n  class Client;\n\n  template <class socket_type>\n  class ClientBase {\n  public:\n    class Content : public std::istream {\n      friend class ClientBase<socket_type>;\n\n    public:\n      size_t size() {\n        return streambuf.size();\n      }\n      /// Convenience function to return std::string. Note that the stream buffer is emptied when this functions is used.\n      std::string string() {\n        std::stringstream ss;\n        ss << rdbuf();\n        return ss.str();\n      }\n\n    private:\n      asio::streambuf &streambuf;\n      Content(asio::streambuf &streambuf) : std::istream(&streambuf), streambuf(streambuf) {}\n    };\n\n    class Response {\n      friend class ClientBase<socket_type>;\n      friend class Client<socket_type>;\n\n    public:\n      std::string http_version, status_code;\n\n      Content content;\n\n      CaseInsensitiveMultimap header;\n\n    private:\n      asio::streambuf content_buffer;\n\n      Response() : content(content_buffer) {}\n    };\n\n    class Config {\n      friend class ClientBase<socket_type>;\n\n    private:\n      Config() {}\n\n    public:\n      /// Set timeout on requests in seconds. Default value: 0 (no timeout).\n      size_t timeout = 0;\n      /// Set connect timeout in seconds. Default value: 0 (Config::timeout is then used instead).\n      size_t timeout_connect = 0;\n      /// Set proxy server (server:port)\n      std::string proxy_server;\n    };\n\n  protected:\n    class Connection {\n    public:\n      Connection(const std::string &host, unsigned short port, const Config &config, std::unique_ptr<socket_type> &&socket)\n          : host(host), port(port), config(config), socket(std::move(socket)) {\n        if(config.proxy_server.empty())\n          query = std::unique_ptr<asio::ip::tcp::resolver::query>(new asio::ip::tcp::resolver::query(host, std::to_string(port)));\n        else {\n          auto proxy_host_port = parse_host_port(config.proxy_server, 8080);\n          query = std::unique_ptr<asio::ip::tcp::resolver::query>(new asio::ip::tcp::resolver::query(proxy_host_port.first, std::to_string(proxy_host_port.second)));\n        }\n      }\n\n      std::string host;\n      unsigned short port;\n      Config config;\n\n      std::unique_ptr<socket_type> socket;\n      bool in_use = false;\n      bool reconnecting = false;\n\n      std::unique_ptr<asio::ip::tcp::resolver::query> query;\n    };\n\n    class Session {\n    public:\n      Session(const std::shared_ptr<asio::io_service> &io_service, const std::shared_ptr<Connection> &connection, std::unique_ptr<asio::streambuf> &&request_buffer)\n          : io_service(io_service), connection(connection), request_buffer(std::move(request_buffer)), response(new Response()) {}\n      std::shared_ptr<asio::io_service> io_service;\n      std::shared_ptr<Connection> connection;\n      std::unique_ptr<asio::streambuf> request_buffer;\n      std::shared_ptr<Response> response;\n      std::function<void(const error_code &)> callback;\n    };\n\n  public:\n    /// Set before calling request\n    Config config;\n\n    /// If you have your own asio::io_service, store its pointer here before calling request().\n    /// When using asynchronous requests, running the io_service is up to the programmer.\n    std::shared_ptr<asio::io_service> io_service;\n\n    virtual ~ClientBase() {}\n\n    /// Convenience function to perform synchronous request. The io_service is run within this function.\n    /// If reusing the io_service for other tasks, please use the asynchronous request functions instead.\n    std::shared_ptr<Response> request(const std::string &method, const std::string &path = std::string(\"/\"),\n                                      string_view content = \"\", const CaseInsensitiveMultimap &header = CaseInsensitiveMultimap()) {\n      std::shared_ptr<Response> response;\n      request(method, path, content, header, [&response](std::shared_ptr<Response> response_, const error_code &ec) {\n        response = response_;\n        if(ec)\n          throw system_error(ec);\n      });\n\n      io_service->reset();\n      io_service->run();\n\n      return response;\n    }\n\n    /// Convenience function to perform synchronous request. The io_service is run within this function.\n    /// If reusing the io_service for other tasks, please use the asynchronous request functions instead.\n    std::shared_ptr<Response> request(const std::string &method, const std::string &path, std::istream &content,\n                                      const CaseInsensitiveMultimap &header = CaseInsensitiveMultimap()) {\n      std::shared_ptr<Response> response;\n      request(method, path, content, header, [&response](std::shared_ptr<Response> response_, const error_code &ec) {\n        response = response_;\n        if(ec)\n          throw system_error(ec);\n      });\n\n      io_service->reset();\n      io_service->run();\n\n      return response;\n    }\n\n    /// Asynchronous request where setting and/or running Client's io_service is required.\n    void request(const std::string &method, const std::string &path, string_view content, const CaseInsensitiveMultimap &header,\n                 std::function<void(std::shared_ptr<Response>, const error_code &)> &&request_callback_) {\n      auto session = std::make_shared<Session>(io_service, get_connection(), create_request_header(method, path, header));\n      auto connection = session->connection;\n      auto response = session->response;\n      auto request_callback = std::make_shared<std::function<void(std::shared_ptr<Response>, const error_code &)>>(std::move(request_callback_));\n      auto connections = this->connections;\n      auto connections_mutex = this->connections_mutex;\n      session->callback = [connection, response, request_callback, connections, connections_mutex](const error_code &ec) {\n        {\n          std::lock_guard<std::mutex> lock(*connections_mutex);\n          connection->in_use = false;\n\n          // Remove unused connections, but keep one open for HTTP persistent connection:\n          size_t unused_connections = 0;\n          for(auto it = connections->begin(); it != connections->end();) {\n            if((*it)->in_use)\n              ++it;\n            else {\n              ++unused_connections;\n              if(unused_connections > 1)\n                it = connections->erase(it);\n              else\n                ++it;\n            }\n          }\n        }\n\n        if(*request_callback)\n          (*request_callback)(response, ec);\n      };\n\n      std::ostream write_stream(session->request_buffer.get());\n      if(content.size() > 0)\n        write_stream << \"Content-Length: \" << content.size() << \"\\r\\n\";\n      write_stream << \"\\r\\n\"\n                   << content;\n\n      Client<socket_type>::connect(session);\n    }\n\n    /// Asynchronous request where setting and/or running Client's io_service is required.\n    void request(const std::string &method, const std::string &path, string_view content,\n                 std::function<void(std::shared_ptr<Response>, const error_code &)> &&request_callback) {\n      request(method, path, content, CaseInsensitiveMultimap(), std::move(request_callback));\n    }\n\n    /// Asynchronous request where setting and/or running Client's io_service is required.\n    void request(const std::string &method, const std::string &path,\n                 std::function<void(std::shared_ptr<Response>, const error_code &)> &&request_callback) {\n      request(method, path, std::string(), CaseInsensitiveMultimap(), std::move(request_callback));\n    }\n\n    /// Asynchronous request where setting and/or running Client's io_service is required.\n    void request(const std::string &method, std::function<void(std::shared_ptr<Response>, const error_code &)> &&request_callback) {\n      request(method, std::string(\"/\"), std::string(), CaseInsensitiveMultimap(), std::move(request_callback));\n    }\n\n    /// Asynchronous request where setting and/or running Client's io_service is required.\n    void request(const std::string &method, const std::string &path, std::istream &content, const CaseInsensitiveMultimap &header,\n                 std::function<void(std::shared_ptr<Response>, const error_code &)> &&request_callback_) {\n      auto session = std::make_shared<Session>(io_service, get_connection(), create_request_header(method, path, header));\n      auto connection = session->connection;\n      auto response = session->response;\n      auto request_callback = std::make_shared<std::function<void(std::shared_ptr<Response>, const error_code &)>>(std::move(request_callback_));\n      auto connections = this->connections;\n      auto connections_mutex = this->connections_mutex;\n      session->callback = [connection, response, request_callback, connections, connections_mutex](const error_code &ec) {\n        {\n          std::lock_guard<std::mutex> lock(*connections_mutex);\n          connection->in_use = false;\n\n          // Remove unused connections, but keep one open for HTTP persistent connection:\n          size_t unused_connections = 0;\n          for(auto it = connections->begin(); it != connections->end();) {\n            if((*it)->in_use)\n              ++it;\n            else {\n              ++unused_connections;\n              if(unused_connections > 1)\n                it = connections->erase(it);\n              else\n                ++it;\n            }\n          }\n        }\n\n        if(*request_callback)\n          (*request_callback)(response, ec);\n      };\n\n      content.seekg(0, std::ios::end);\n      auto content_length = content.tellg();\n      content.seekg(0, std::ios::beg);\n      std::ostream write_stream(session->request_buffer.get());\n      if(content_length > 0)\n        write_stream << \"Content-Length: \" << content_length << \"\\r\\n\";\n      write_stream << \"\\r\\n\";\n      if(content_length > 0)\n        write_stream << content.rdbuf();\n\n      Client<socket_type>::connect(session);\n    }\n\n    /// Asynchronous request where setting and/or running Client's io_service is required.\n    void request(const std::string &method, const std::string &path, std::istream &content,\n                 std::function<void(std::shared_ptr<Response>, const error_code &)> &&request_callback) {\n      request(method, path, content, CaseInsensitiveMultimap(), std::move(request_callback));\n    }\n\n  protected:\n    std::string host;\n    unsigned short port;\n\n    std::shared_ptr<std::vector<std::shared_ptr<Connection>>> connections;\n    std::shared_ptr<std::mutex> connections_mutex;\n\n    ClientBase(const std::string &host_port, unsigned short default_port)\n        : io_service(new asio::io_service()), connections(new std::vector<std::shared_ptr<Connection>>()), connections_mutex(new std::mutex()) {\n      auto parsed_host_port = parse_host_port(host_port, default_port);\n      host = parsed_host_port.first;\n      port = parsed_host_port.second;\n    }\n\n    std::shared_ptr<Connection> get_connection() {\n      std::shared_ptr<Connection> connection;\n      std::lock_guard<std::mutex> lock(*connections_mutex);\n      for(auto it = connections->begin(); it != connections->end(); ++it) {\n        if(!(*it)->in_use && !connection) {\n          connection = *it;\n          break;\n        }\n      }\n      if(!connection) {\n        connection = create_connection();\n        connections->emplace_back(connection);\n      }\n      connection->reconnecting = false;\n      connection->in_use = true;\n      return connection;\n    }\n\n    virtual std::shared_ptr<Connection> create_connection() = 0;\n\n    std::unique_ptr<asio::streambuf> create_request_header(const std::string &method, const std::string &path, const CaseInsensitiveMultimap &header) const {\n      auto corrected_path = path;\n      if(corrected_path == \"\")\n        corrected_path = \"/\";\n      if(!config.proxy_server.empty() && std::is_same<socket_type, asio::ip::tcp::socket>::value)\n        corrected_path = \"http://\" + host + ':' + std::to_string(port) + corrected_path;\n\n      std::unique_ptr<asio::streambuf> request_buffer(new asio::streambuf());\n      std::ostream write_stream(request_buffer.get());\n      write_stream << method << \" \" << corrected_path << \" HTTP/1.1\\r\\n\";\n      write_stream << \"Host: \" << host << \"\\r\\n\";\n      for(auto &h : header)\n        write_stream << h.first << \": \" << h.second << \"\\r\\n\";\n      return request_buffer;\n    }\n\n    static std::pair<std::string, unsigned short> parse_host_port(const std::string &host_port, unsigned short default_port) {\n      std::pair<std::string, unsigned short> parsed_host_port;\n      size_t host_end = host_port.find(':');\n      if(host_end == std::string::npos) {\n        parsed_host_port.first = host_port;\n        parsed_host_port.second = default_port;\n      }\n      else {\n        parsed_host_port.first = host_port.substr(0, host_end);\n        parsed_host_port.second = static_cast<unsigned short>(stoul(host_port.substr(host_end + 1)));\n      }\n      return parsed_host_port;\n    }\n\n    static std::shared_ptr<asio::deadline_timer> get_timeout_timer(const std::shared_ptr<Session> &session, size_t timeout = 0) {\n      if(timeout == 0)\n        timeout = session->connection->config.timeout;\n      if(timeout == 0)\n        return nullptr;\n\n      auto timer = std::make_shared<asio::deadline_timer>(*session->io_service);\n      timer->expires_from_now(boost::posix_time::seconds(timeout));\n      timer->async_wait([session](const error_code &ec) {\n        if(!ec)\n          close(session);\n      });\n      return timer;\n    }\n\n    static void parse_response_header(const std::shared_ptr<Response> &response) {\n      std::string line;\n      getline(response->content, line);\n      size_t version_end = line.find(' ');\n      if(version_end != std::string::npos) {\n        if(5 < line.size())\n          response->http_version = line.substr(5, version_end - 5);\n        if((version_end + 1) < line.size())\n          response->status_code = line.substr(version_end + 1, line.size() - (version_end + 1) - 1);\n\n        getline(response->content, line);\n        size_t param_end;\n        while((param_end = line.find(':')) != std::string::npos) {\n          size_t value_start = param_end + 1;\n          if((value_start) < line.size()) {\n            if(line[value_start] == ' ')\n              value_start++;\n            if(value_start < line.size())\n              response->header.insert(std::make_pair(line.substr(0, param_end), line.substr(value_start, line.size() - value_start - 1)));\n          }\n\n          getline(response->content, line);\n        }\n      }\n    }\n\n    static void write(const std::shared_ptr<Session> &session) {\n      auto timer = get_timeout_timer(session);\n      asio::async_write(*session->connection->socket, session->request_buffer->data(), [session, timer](const error_code &ec, size_t /*bytes_transferred*/) {\n        if(timer)\n          timer->cancel();\n        if(!ec)\n          read(session);\n        else {\n          close(session);\n          session->callback(ec);\n        }\n      });\n    }\n\n    static void read(const std::shared_ptr<Session> &session) {\n      auto timer = get_timeout_timer(session);\n      asio::async_read_until(*session->connection->socket, session->response->content_buffer, \"\\r\\n\\r\\n\", [session, timer](const error_code &ec, size_t bytes_transferred) {\n        if(timer)\n          timer->cancel();\n        if(!ec) {\n          session->connection->reconnecting = false;\n\n          size_t num_additional_bytes = session->response->content_buffer.size() - bytes_transferred;\n\n          parse_response_header(session->response);\n\n          auto header_it = session->response->header.find(\"Content-Length\");\n          if(header_it != session->response->header.end()) {\n            auto content_length = stoull(header_it->second);\n            if(content_length > num_additional_bytes) {\n              auto timer = get_timeout_timer(session);\n              asio::async_read(*session->connection->socket, session->response->content_buffer, asio::transfer_exactly(content_length - num_additional_bytes), [session, timer](const error_code &ec, size_t /*bytes_transferred*/) {\n                if(timer)\n                  timer->cancel();\n                if(!ec)\n                  session->callback(ec);\n                else {\n                  close(session);\n                  session->callback(ec);\n                }\n              });\n            }\n            else\n              session->callback(ec);\n          }\n          else if((header_it = session->response->header.find(\"Transfer-Encoding\")) != session->response->header.end() && header_it->second == \"chunked\") {\n            auto tmp_streambuf = std::make_shared<asio::streambuf>();\n            read_chunked(session, tmp_streambuf);\n          }\n          else if(session->response->http_version < \"1.1\" || ((header_it = session->response->header.find(\"Session\")) != session->response->header.end() && header_it->second == \"close\")) {\n            auto timer = get_timeout_timer(session);\n            asio::async_read(*session->connection->socket, session->response->content_buffer, [session, timer](const error_code &ec, size_t /*bytes_transferred*/) {\n              if(timer)\n                timer->cancel();\n              if(!ec)\n                session->callback(ec);\n              else {\n                close(session);\n                if(ec == asio::error::eof) {\n                  error_code ec;\n                  session->callback(ec);\n                }\n                else\n                  session->callback(ec);\n              }\n            });\n          }\n          else\n            session->callback(ec);\n        }\n        else {\n          if(!session->connection->reconnecting) {\n            session->connection->reconnecting = true;\n            close(session);\n            Client<socket_type>::connect(session);\n          }\n          else {\n            close(session);\n            session->callback(ec);\n          }\n        }\n      });\n    }\n\n    static void read_chunked(const std::shared_ptr<Session> &session, const std::shared_ptr<asio::streambuf> &tmp_streambuf) {\n      auto timer = get_timeout_timer(session);\n      asio::async_read_until(*session->connection->socket, session->response->content_buffer, \"\\r\\n\", [session, tmp_streambuf, timer](const error_code &ec, size_t bytes_transferred) {\n        if(timer)\n          timer->cancel();\n        if(!ec) {\n          std::string line;\n          getline(session->response->content, line);\n          bytes_transferred -= line.size() + 1;\n          line.pop_back();\n          std::streamsize length = stol(line, 0, 16);\n\n          auto num_additional_bytes = static_cast<std::streamsize>(session->response->content_buffer.size() - bytes_transferred);\n\n          auto post_process = [session, tmp_streambuf, length] {\n            std::ostream tmp_stream(tmp_streambuf.get());\n            if(length > 0) {\n              std::vector<char> buffer(static_cast<size_t>(length));\n              session->response->content.read(&buffer[0], length);\n              tmp_stream.write(&buffer[0], length);\n            }\n\n            //Remove \"\\r\\n\"\n            session->response->content.get();\n            session->response->content.get();\n\n            if(length > 0)\n              read_chunked(session, tmp_streambuf);\n            else {\n              std::ostream response_stream(&session->response->content_buffer);\n              response_stream << tmp_stream.rdbuf();\n              error_code ec;\n              session->callback(ec);\n            }\n          };\n\n          if((2 + length) > num_additional_bytes) {\n            auto timer = get_timeout_timer(session);\n            asio::async_read(*session->connection->socket, session->response->content_buffer, asio::transfer_exactly(2 + length - num_additional_bytes), [session, post_process, timer](const error_code &ec, size_t /*bytes_transferred*/) {\n              if(timer)\n                timer->cancel();\n              if(!ec)\n                post_process();\n              else {\n                close(session);\n                session->callback(ec);\n              }\n            });\n          }\n          else\n            post_process();\n        }\n        else {\n          close(session);\n          session->callback(ec);\n        }\n      });\n    }\n\n    static void close(const std::shared_ptr<Session> &session) {\n      error_code ec;\n      session->connection->socket->lowest_layer().shutdown(asio::ip::tcp::socket::shutdown_both, ec);\n      session->connection->socket->lowest_layer().close(ec);\n    }\n  };\n\n  template <class socket_type>\n  class Client : public ClientBase<socket_type> {};\n\n  typedef asio::ip::tcp::socket HTTP;\n\n  template <>\n  class Client<HTTP> : public ClientBase<HTTP> {\n  public:\n    friend ClientBase<HTTP>;\n\n    Client(const std::string &server_port_path) : ClientBase<HTTP>::ClientBase(server_port_path, 80) {}\n\n  protected:\n    std::shared_ptr<Connection> create_connection() override {\n      return std::make_shared<Connection>(host, port, config, std::unique_ptr<HTTP>(new HTTP(*io_service)));\n    }\n\n    static void connect(const std::shared_ptr<Session> &session) {\n      if(!session->connection->socket->lowest_layer().is_open()) {\n        auto resolver = std::make_shared<asio::ip::tcp::resolver>(*session->io_service);\n        auto timer = get_timeout_timer(session, session->connection->config.timeout_connect);\n        resolver->async_resolve(*session->connection->query, [session, timer, resolver](const error_code &ec, asio::ip::tcp::resolver::iterator it) {\n          if(timer)\n            timer->cancel();\n          if(!ec) {\n            auto timer = get_timeout_timer(session, session->connection->config.timeout_connect);\n            asio::async_connect(*session->connection->socket, it, [session, timer, resolver](const error_code &ec, asio::ip::tcp::resolver::iterator /*it*/) {\n              if(timer)\n                timer->cancel();\n              if(!ec) {\n                asio::ip::tcp::no_delay option(true);\n                session->connection->socket->set_option(option);\n                write(session);\n              }\n              else {\n                close(session);\n                session->callback(ec);\n              }\n            });\n          }\n          else {\n            close(session);\n            session->callback(ec);\n          }\n        });\n      }\n      else\n        write(session);\n    }\n  };\n} // namespace SimpleWeb\n\n#endif /* CLIENT_HTTP_HPP */\n"
  },
  {
    "path": "client_https.hpp",
    "content": "#ifndef CLIENT_HTTPS_HPP\n#define CLIENT_HTTPS_HPP\n\n#include \"client_http.hpp\"\n\n#ifdef USE_STANDALONE_ASIO\n#include <asio/ssl.hpp>\n#else\n#include <boost/asio/ssl.hpp>\n#endif\n\nnamespace SimpleWeb {\n  typedef asio::ssl::stream<asio::ip::tcp::socket> HTTPS;\n\n  template <>\n  class Client<HTTPS> : public ClientBase<HTTPS> {\n  public:\n    friend ClientBase<HTTPS>;\n\n    Client(const std::string &server_port_path, bool verify_certificate = true, const std::string &cert_file = std::string(),\n           const std::string &private_key_file = std::string(), const std::string &verify_file = std::string())\n        : ClientBase<HTTPS>::ClientBase(server_port_path, 443), context(asio::ssl::context::tlsv12) {\n      if(cert_file.size() > 0 && private_key_file.size() > 0) {\n        context.use_certificate_chain_file(cert_file);\n        context.use_private_key_file(private_key_file, asio::ssl::context::pem);\n      }\n\n      if(verify_certificate)\n        context.set_verify_callback(asio::ssl::rfc2818_verification(host));\n\n      if(verify_file.size() > 0)\n        context.load_verify_file(verify_file);\n      else\n        context.set_default_verify_paths();\n\n      if(verify_file.size() > 0 || verify_certificate)\n        context.set_verify_mode(asio::ssl::verify_peer);\n      else\n        context.set_verify_mode(asio::ssl::verify_none);\n    }\n\n  protected:\n    asio::ssl::context context;\n\n    std::shared_ptr<Connection> create_connection() override {\n      return std::make_shared<Connection>(host, port, config, std::unique_ptr<HTTPS>(new HTTPS(*io_service, context)));\n    }\n\n    static void connect(const std::shared_ptr<Session> &session) {\n      if(!session->connection->socket->lowest_layer().is_open()) {\n        auto resolver = std::make_shared<asio::ip::tcp::resolver>(*session->io_service);\n        resolver->async_resolve(*session->connection->query, [session, resolver](const error_code &ec, asio::ip::tcp::resolver::iterator it) {\n          if(!ec) {\n            auto timer = get_timeout_timer(session, session->connection->config.timeout_connect);\n            asio::async_connect(session->connection->socket->lowest_layer(), it, [session, resolver, timer](const error_code &ec, asio::ip::tcp::resolver::iterator /*it*/) {\n              if(timer)\n                timer->cancel();\n              if(!ec) {\n                asio::ip::tcp::no_delay option(true);\n                session->connection->socket->lowest_layer().set_option(option);\n\n                if(!session->connection->config.proxy_server.empty()) {\n                  auto write_buffer = std::make_shared<asio::streambuf>();\n                  std::ostream write_stream(write_buffer.get());\n                  auto host_port = session->connection->host + ':' + std::to_string(session->connection->port);\n                  write_stream << \"CONNECT \" + host_port + \" HTTP/1.1\\r\\n\"\n                               << \"Host: \" << host_port << \"\\r\\n\\r\\n\";\n                  auto timer = get_timeout_timer(session, session->connection->config.timeout_connect);\n                  asio::async_write(session->connection->socket->next_layer(), *write_buffer, [session, write_buffer, timer](const error_code &ec, size_t /*bytes_transferred*/) {\n                    if(timer)\n                      timer->cancel();\n                    if(!ec) {\n                      std::shared_ptr<Response> response(new Response());\n                      auto timer = get_timeout_timer(session, session->connection->config.timeout_connect);\n                      asio::async_read_until(session->connection->socket->next_layer(), response->content_buffer, \"\\r\\n\\r\\n\", [session, response, timer](const error_code &ec, size_t /*bytes_transferred*/) {\n                        if(timer)\n                          timer->cancel();\n                        if(!ec) {\n                          parse_response_header(response);\n                          if(response->status_code.empty() || response->status_code.compare(0, 3, \"200\") != 0) {\n                            close(session);\n                            session->callback(make_error_code::make_error_code(errc::permission_denied));\n                          }\n                          else\n                            handshake(session);\n                        }\n                        else {\n                          close(session);\n                          session->callback(ec);\n                        }\n                      });\n                    }\n                    else {\n                      close(session);\n                      session->callback(ec);\n                    }\n                  });\n                }\n                else\n                  handshake(session);\n              }\n              else {\n                close(session);\n                session->callback(ec);\n              }\n            });\n          }\n          else {\n            close(session);\n            session->callback(ec);\n          }\n        });\n      }\n      else\n        write(session);\n    }\n\n    static void handshake(const std::shared_ptr<Session> &session) {\n      auto timer = get_timeout_timer(session, session->connection->config.timeout_connect);\n      session->connection->socket->async_handshake(asio::ssl::stream_base::client, [session, timer](const error_code &ec) {\n        if(timer)\n          timer->cancel();\n        if(!ec)\n          write(session);\n        else {\n          close(session);\n          session->callback(ec);\n        }\n      });\n    }\n  };\n} // namespace SimpleWeb\n\n#endif /* CLIENT_HTTPS_HPP */\n"
  },
  {
    "path": "crypto.hpp",
    "content": "#ifndef SIMPLE_WEB_CRYPTO_HPP\n#define SIMPLE_WEB_CRYPTO_HPP\n\n#include <cmath>\n#include <iomanip>\n#include <istream>\n#include <sstream>\n#include <string>\n#include <vector>\n\n//Moving these to a seperate namespace for minimal global namespace cluttering does not work with clang++\n#include <openssl/buffer.h>\n#include <openssl/evp.h>\n#include <openssl/md5.h>\n#include <openssl/sha.h>\n\nnamespace SimpleWeb {\n//TODO 2017: remove workaround for MSVS 2012\n#if _MSC_VER == 1700              //MSVS 2012 has no definition for round()\n  inline double round(double x) { //custom definition of round() for positive numbers\n    return floor(x + 0.5);\n  }\n#endif\n\n  class Crypto {\n    const static size_t buffer_size = 131072;\n\n  public:\n    class Base64 {\n    public:\n      static std::string encode(const std::string &ascii) {\n        std::string base64;\n\n        BIO *bio, *b64;\n        BUF_MEM *bptr = BUF_MEM_new();\n\n        b64 = BIO_new(BIO_f_base64());\n        BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);\n        bio = BIO_new(BIO_s_mem());\n        BIO_push(b64, bio);\n        BIO_set_mem_buf(b64, bptr, BIO_CLOSE);\n\n        //Write directly to base64-buffer to avoid copy\n        int base64_length = static_cast<int>(round(4 * ceil((double)ascii.size() / 3.0)));\n        base64.resize(base64_length);\n        bptr->length = 0;\n        bptr->max = base64_length + 1;\n        bptr->data = (char *)&base64[0];\n\n        BIO_write(b64, &ascii[0], static_cast<int>(ascii.size()));\n        BIO_flush(b64);\n\n        //To keep &base64[0] through BIO_free_all(b64)\n        bptr->length = 0;\n        bptr->max = 0;\n        bptr->data = nullptr;\n\n        BIO_free_all(b64);\n\n        return base64;\n      }\n\n      static std::string decode(const std::string &base64) {\n        std::string ascii;\n\n        //Resize ascii, however, the size is a up to two bytes too large.\n        ascii.resize((6 * base64.size()) / 8);\n        BIO *b64, *bio;\n\n        b64 = BIO_new(BIO_f_base64());\n        BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);\n        bio = BIO_new_mem_buf((char *)&base64[0], static_cast<int>(base64.size()));\n        bio = BIO_push(b64, bio);\n\n        int decoded_length = BIO_read(bio, &ascii[0], static_cast<int>(ascii.size()));\n        ascii.resize(decoded_length);\n\n        BIO_free_all(b64);\n\n        return ascii;\n      }\n    };\n\n    /// Return hex string from bytes in input string.\n    static std::string to_hex_string(const std::string &input) {\n      std::stringstream hex_stream;\n      hex_stream << std::hex << std::internal << std::setfill('0');\n      for(auto &byte : input)\n        hex_stream << std::setw(2) << static_cast<int>(static_cast<unsigned char>(byte));\n      return hex_stream.str();\n    }\n\n    static std::string md5(const std::string &input, size_t iterations = 1) {\n      std::string hash;\n\n      hash.resize(128 / 8);\n      MD5(reinterpret_cast<const unsigned char *>(&input[0]), input.size(), reinterpret_cast<unsigned char *>(&hash[0]));\n\n      for(size_t c = 1; c < iterations; ++c)\n        MD5(reinterpret_cast<const unsigned char *>(&hash[0]), hash.size(), reinterpret_cast<unsigned char *>(&hash[0]));\n\n      return hash;\n    }\n\n    static std::string md5(std::istream &stream, size_t iterations = 1) {\n      MD5_CTX context;\n      MD5_Init(&context);\n      std::streamsize read_length;\n      std::vector<char> buffer(buffer_size);\n      while((read_length = stream.read(&buffer[0], buffer_size).gcount()) > 0)\n        MD5_Update(&context, buffer.data(), read_length);\n      std::string hash;\n      hash.resize(128 / 8);\n      MD5_Final(reinterpret_cast<unsigned char *>(&hash[0]), &context);\n\n      for(size_t c = 1; c < iterations; ++c)\n        MD5(reinterpret_cast<const unsigned char *>(&hash[0]), hash.size(), reinterpret_cast<unsigned char *>(&hash[0]));\n\n      return hash;\n    }\n\n    static std::string sha1(const std::string &input, size_t iterations = 1) {\n      std::string hash;\n\n      hash.resize(160 / 8);\n      SHA1(reinterpret_cast<const unsigned char *>(&input[0]), input.size(), reinterpret_cast<unsigned char *>(&hash[0]));\n\n      for(size_t c = 1; c < iterations; ++c)\n        SHA1(reinterpret_cast<const unsigned char *>(&hash[0]), hash.size(), reinterpret_cast<unsigned char *>(&hash[0]));\n\n      return hash;\n    }\n\n    static std::string sha1(std::istream &stream, size_t iterations = 1) {\n      SHA_CTX context;\n      SHA1_Init(&context);\n      std::streamsize read_length;\n      std::vector<char> buffer(buffer_size);\n      while((read_length = stream.read(&buffer[0], buffer_size).gcount()) > 0)\n        SHA1_Update(&context, buffer.data(), read_length);\n      std::string hash;\n      hash.resize(160 / 8);\n      SHA1_Final(reinterpret_cast<unsigned char *>(&hash[0]), &context);\n\n      for(size_t c = 1; c < iterations; ++c)\n        SHA1(reinterpret_cast<const unsigned char *>(&hash[0]), hash.size(), reinterpret_cast<unsigned char *>(&hash[0]));\n\n      return hash;\n    }\n\n    static std::string sha256(const std::string &input, size_t iterations = 1) {\n      std::string hash;\n\n      hash.resize(256 / 8);\n      SHA256(reinterpret_cast<const unsigned char *>(&input[0]), input.size(), reinterpret_cast<unsigned char *>(&hash[0]));\n\n      for(size_t c = 1; c < iterations; ++c)\n        SHA256(reinterpret_cast<const unsigned char *>(&hash[0]), hash.size(), reinterpret_cast<unsigned char *>(&hash[0]));\n\n      return hash;\n    }\n\n    static std::string sha256(std::istream &stream, size_t iterations = 1) {\n      SHA256_CTX context;\n      SHA256_Init(&context);\n      std::streamsize read_length;\n      std::vector<char> buffer(buffer_size);\n      while((read_length = stream.read(&buffer[0], buffer_size).gcount()) > 0)\n        SHA256_Update(&context, buffer.data(), read_length);\n      std::string hash;\n      hash.resize(256 / 8);\n      SHA256_Final(reinterpret_cast<unsigned char *>(&hash[0]), &context);\n\n      for(size_t c = 1; c < iterations; ++c)\n        SHA256(reinterpret_cast<const unsigned char *>(&hash[0]), hash.size(), reinterpret_cast<unsigned char *>(&hash[0]));\n\n      return hash;\n    }\n\n    static std::string sha512(const std::string &input, size_t iterations = 1) {\n      std::string hash;\n\n      hash.resize(512 / 8);\n      SHA512(reinterpret_cast<const unsigned char *>(&input[0]), input.size(), reinterpret_cast<unsigned char *>(&hash[0]));\n\n      for(size_t c = 1; c < iterations; ++c)\n        SHA512(reinterpret_cast<const unsigned char *>(&hash[0]), hash.size(), reinterpret_cast<unsigned char *>(&hash[0]));\n\n      return hash;\n    }\n\n    static std::string sha512(std::istream &stream, size_t iterations = 1) {\n      SHA512_CTX context;\n      SHA512_Init(&context);\n      std::streamsize read_length;\n      std::vector<char> buffer(buffer_size);\n      while((read_length = stream.read(&buffer[0], buffer_size).gcount()) > 0)\n        SHA512_Update(&context, buffer.data(), read_length);\n      std::string hash;\n      hash.resize(512 / 8);\n      SHA512_Final(reinterpret_cast<unsigned char *>(&hash[0]), &context);\n\n      for(size_t c = 1; c < iterations; ++c)\n        SHA512(reinterpret_cast<const unsigned char *>(&hash[0]), hash.size(), reinterpret_cast<unsigned char *>(&hash[0]));\n\n      return hash;\n    }\n\n    /// key_size is number of bytes of the returned key.\n    static std::string pbkdf2(const std::string &password, const std::string &salt, int iterations, int key_size) {\n      std::string key;\n      key.resize(key_size);\n      PKCS5_PBKDF2_HMAC_SHA1(password.c_str(), password.size(),\n                             reinterpret_cast<const unsigned char *>(salt.c_str()), salt.size(), iterations,\n                             key_size, reinterpret_cast<unsigned char *>(&key[0]));\n      return key;\n    }\n  };\n}\n#endif /* SIMPLE_WEB_CRYPTO_HPP */\n"
  },
  {
    "path": "http_examples.cpp",
    "content": "#include \"client_http.hpp\"\n#include \"server_http.hpp\"\n\n//Added for the json-example\n#define BOOST_SPIRIT_THREADSAFE\n#include <boost/property_tree/json_parser.hpp>\n#include <boost/property_tree/ptree.hpp>\n\n//Added for the default_resource example\n#include <algorithm>\n#include <boost/filesystem.hpp>\n#include <fstream>\n#include <vector>\n#ifdef HAVE_OPENSSL\n#include \"crypto.hpp\"\n#endif\n\nusing namespace std;\n//Added for the json-example:\nusing namespace boost::property_tree;\n\ntypedef SimpleWeb::Server<SimpleWeb::HTTP> HttpServer;\ntypedef SimpleWeb::Client<SimpleWeb::HTTP> HttpClient;\n\n//Added for the default_resource example\nvoid default_resource_send(const HttpServer &server, const shared_ptr<HttpServer::Response> &response, const shared_ptr<ifstream> &ifs);\n\nint main() {\n  //HTTP-server at port 8080 using 1 thread\n  //Unless you do more heavy non-threaded processing in the resources,\n  //1 thread is usually faster than several threads\n  HttpServer server;\n  server.config.port = 8080;\n\n  //Add resources using path-regex and method-string, and an anonymous function\n  //POST-example for the path /string, responds the posted string\n  server.resource[\"^/string$\"][\"POST\"] = [](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> request) {\n    //Retrieve string:\n    auto content = request->content.string();\n    //request->content.string() is a convenience function for:\n    //stringstream ss;\n    //ss << request->content.rdbuf();\n    //auto content=ss.str();\n\n    *response << \"HTTP/1.1 200 OK\\r\\nContent-Length: \" << content.length() << \"\\r\\n\\r\\n\"\n              << content;\n\n\n    // Alternatively, use one of the convenience functions, for instance:\n    // response->write(content);\n  };\n\n  //POST-example for the path /json, responds firstName+\" \"+lastName from the posted json\n  //Responds with an appropriate error message if the posted json is not valid, or if firstName or lastName is missing\n  //Example posted json:\n  //{\n  //  \"firstName\": \"John\",\n  //  \"lastName\": \"Smith\",\n  //  \"age\": 25\n  //}\n  server.resource[\"^/json$\"][\"POST\"] = [](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> request) {\n    try {\n      ptree pt;\n      read_json(request->content, pt);\n\n      auto name = pt.get<string>(\"firstName\") + \" \" + pt.get<string>(\"lastName\");\n\n      *response << \"HTTP/1.1 200 OK\\r\\n\"\n                << \"Content-Type: application/json\\r\\n\"\n                << \"Content-Length: \" << name.length() << \"\\r\\n\\r\\n\"\n                << name;\n    }\n    catch(const exception &e) {\n      *response << \"HTTP/1.1 400 Bad Request\\r\\nContent-Length: \" << strlen(e.what()) << \"\\r\\n\\r\\n\"\n                << e.what();\n    }\n\n\n    // Alternatively, using a convenience function:\n    // try {\n    //     ptree pt;\n    //     read_json(request->content, pt);\n\n    //     auto name=pt.get<string>(\"firstName\")+\" \"+pt.get<string>(\"lastName\");\n    //     response->write(name, {{\"Content-Type\", \"application/json\"}});\n    // }\n    // catch(const exception &e) {\n    //     response->write(SimpleWeb::StatusCode::client_error_bad_request, e.what());\n    // }\n  };\n\n  //GET-example for the path /info\n  //Responds with request-information\n  server.resource[\"^/info$\"][\"GET\"] = [](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> request) {\n    stringstream stream;\n    stream << \"<h1>Request from \" << request->remote_endpoint_address << \" (\" << request->remote_endpoint_port << \")</h1>\";\n    stream << request->method << \" \" << request->path << \" HTTP/\" << request->http_version << \"<br>\";\n    for(auto &header : request->header)\n      stream << header.first << \": \" << header.second << \"<br>\";\n\n    //find length of content_stream (length received using content_stream.tellp())\n    stream.seekp(0, ios::end);\n\n    *response << \"HTTP/1.1 200 OK\\r\\nContent-Length: \" << stream.tellp() << \"\\r\\n\\r\\n\"\n              << stream.rdbuf();\n\n\n    // Alternatively, using a convenience function:\n    // stringstream stream;\n    // stream << \"<h1>Request from \" << request->remote_endpoint_address << \" (\" << request->remote_endpoint_port << \")</h1>\";\n    // stream << request->method << \" \" << request->path << \" HTTP/\" << request->http_version << \"<br>\";\n    // for(auto &header: request->header)\n    //     stream << header.first << \": \" << header.second << \"<br>\";\n    // response->write(stream);\n  };\n\n  //GET-example for the path /match/[number], responds with the matched string in path (number)\n  //For instance a request GET /match/123 will receive: 123\n  server.resource[\"^/match/([0-9]+)$\"][\"GET\"] = [](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> request) {\n    string number = request->path_match[1];\n    *response << \"HTTP/1.1 200 OK\\r\\nContent-Length: \" << number.length() << \"\\r\\n\\r\\n\"\n              << number;\n\n\n    // Alternatively, using a convenience function:\n    // response->write(request->path_match[1]);\n  };\n\n  //Get example simulating heavy work in a separate thread\n  server.resource[\"^/work$\"][\"GET\"] = [](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> /*request*/) {\n    thread work_thread([response] {\n      this_thread::sleep_for(chrono::seconds(5));\n      response->write(\"Work done\");\n    });\n    work_thread.detach();\n  };\n\n  //Default GET-example. If no other matches, this anonymous function will be called.\n  //Will respond with content in the web/-directory, and its subdirectories.\n  //Default file: index.html\n  //Can for instance be used to retrieve an HTML 5 client that uses REST-resources on this server\n  server.default_resource[\"GET\"] = [&server](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> request) {\n    try {\n      auto web_root_path = boost::filesystem::canonical(\"web\");\n      auto path = boost::filesystem::canonical(web_root_path / request->path);\n      //Check if path is within web_root_path\n      if(distance(web_root_path.begin(), web_root_path.end()) > distance(path.begin(), path.end()) ||\n         !equal(web_root_path.begin(), web_root_path.end(), path.begin()))\n        throw invalid_argument(\"path must be within root path\");\n      if(boost::filesystem::is_directory(path))\n        path /= \"index.html\";\n\n      SimpleWeb::CaseInsensitiveMultimap header;\n\n//    Uncomment the following line to enable Cache-Control\n//    header.emplace(\"Cache-Control\", \"max-age=86400\");\n\n#ifdef HAVE_OPENSSL\n//    Uncomment the following lines to enable ETag\n//    {\n//      ifstream ifs(path.string(), ifstream::in | ios::binary);\n//      if(ifs) {\n//        auto hash = SimpleWeb::Crypto::to_hex_string(SimpleWeb::Crypto::md5(ifs));\n//        header.emplace(\"ETag\", \"\\\"\" + hash + \"\\\"\");\n//        auto it = request->header.find(\"If-None-Match\");\n//        if(it != request->header.end()) {\n//          if(!it->second.empty() && it->second.compare(1, hash.size(), hash) == 0) {\n//            response->write(SimpleWeb::StatusCode::redirection_not_modified, header);\n//            return;\n//          }\n//        }\n//      }\n//      else\n//        throw invalid_argument(\"could not read file\");\n//    }\n#endif\n\n      auto ifs = make_shared<ifstream>();\n      ifs->open(path.string(), ifstream::in | ios::binary | ios::ate);\n\n      if(*ifs) {\n        auto length = ifs->tellg();\n        ifs->seekg(0, ios::beg);\n\n        header.emplace(\"Content-Length\", to_string(length));\n        response->write(header);\n        default_resource_send(server, response, ifs);\n      }\n      else\n        throw invalid_argument(\"could not read file\");\n    }\n    catch(const exception &e) {\n      response->write(SimpleWeb::StatusCode::client_error_bad_request, \"Could not open path \" + request->path + \": \" + e.what());\n    }\n  };\n\n  server.on_error = [](std::shared_ptr<HttpServer::Request> /*request*/, const SimpleWeb::error_code & /*ec*/) {\n    // handle errors here\n  };\n\n  thread server_thread([&server]() {\n    //Start server\n    server.start();\n  });\n\n  //Wait for server to start so that the client can connect\n  this_thread::sleep_for(chrono::seconds(1));\n\n  //Client examples\n  HttpClient client(\"localhost:8080\");\n\n  // synchronous request examples\n  auto r1 = client.request(\"GET\", \"/match/123\");\n  cout << r1->content.rdbuf() << endl; // Alternatively, use the convenience function r1->content.string()\n\n  string json_string = \"{\\\"firstName\\\": \\\"John\\\",\\\"lastName\\\": \\\"Smith\\\",\\\"age\\\": 25}\";\n  auto r2 = client.request(\"POST\", \"/string\", json_string);\n  cout << r2->content.rdbuf() << endl;\n\n  // asynchronous request example\n  client.request(\"POST\", \"/json\", json_string, [](std::shared_ptr<HttpClient::Response> response, const SimpleWeb::error_code &ec) {\n    if(!ec)\n      cout << response->content.rdbuf() << endl;\n  });\n  client.io_service->reset(); // needed because the io_service has been run already in the synchronous examples\n  client.io_service->run();\n\n  server_thread.join();\n}\n\nvoid default_resource_send(const HttpServer &server, const shared_ptr<HttpServer::Response> &response, const shared_ptr<ifstream> &ifs) {\n  //read and send 128 KB at a time\n  static vector<char> buffer(131072); // Safe when server is running on one thread\n  streamsize read_length;\n  if((read_length = ifs->read(&buffer[0], buffer.size()).gcount()) > 0) {\n    response->write(&buffer[0], read_length);\n    if(read_length == static_cast<streamsize>(buffer.size())) {\n      server.send(response, [&server, response, ifs](const SimpleWeb::error_code &ec) {\n        if(!ec)\n          default_resource_send(server, response, ifs);\n        else\n          cerr << \"Connection interrupted\" << endl;\n      });\n    }\n  }\n}\n"
  },
  {
    "path": "https_examples.cpp",
    "content": "#include \"client_https.hpp\"\n#include \"server_https.hpp\"\n\n//Added for the json-example\n#define BOOST_SPIRIT_THREADSAFE\n#include <boost/property_tree/json_parser.hpp>\n#include <boost/property_tree/ptree.hpp>\n\n//Added for the default_resource example\n#include \"crypto.hpp\"\n#include <algorithm>\n#include <boost/filesystem.hpp>\n#include <fstream>\n#include <vector>\n\nusing namespace std;\n//Added for the json-example:\nusing namespace boost::property_tree;\n\ntypedef SimpleWeb::Server<SimpleWeb::HTTPS> HttpsServer;\ntypedef SimpleWeb::Client<SimpleWeb::HTTPS> HttpsClient;\n\n//Added for the default_resource example\nvoid default_resource_send(const HttpsServer &server, const shared_ptr<HttpsServer::Response> &response, const shared_ptr<ifstream> &ifs);\n\nint main() {\n  //HTTPS-server at port 8080 using 1 thread\n  //Unless you do more heavy non-threaded processing in the resources,\n  //1 thread is usually faster than several threads\n  HttpsServer server(\"server.crt\", \"server.key\");\n  server.config.port = 8080;\n\n  //Add resources using path-regex and method-string, and an anonymous function\n  //POST-example for the path /string, responds the posted string\n  server.resource[\"^/string$\"][\"POST\"] = [](shared_ptr<HttpsServer::Response> response, shared_ptr<HttpsServer::Request> request) {\n    //Retrieve string:\n    auto content = request->content.string();\n    //request->content.string() is a convenience function for:\n    //stringstream ss;\n    //ss << request->content.rdbuf();\n    //auto content=ss.str();\n\n    *response << \"HTTP/1.1 200 OK\\r\\nContent-Length: \" << content.length() << \"\\r\\n\\r\\n\"\n              << content;\n\n\n    // Alternatively, use one of the convenience functions, for instance:\n    // response->write(content);\n  };\n\n  //POST-example for the path /json, responds firstName+\" \"+lastName from the posted json\n  //Responds with an appropriate error message if the posted json is not valid, or if firstName or lastName is missing\n  //Example posted json:\n  //{\n  //  \"firstName\": \"John\",\n  //  \"lastName\": \"Smith\",\n  //  \"age\": 25\n  //}\n  server.resource[\"^/json$\"][\"POST\"] = [](shared_ptr<HttpsServer::Response> response, shared_ptr<HttpsServer::Request> request) {\n    try {\n      ptree pt;\n      read_json(request->content, pt);\n\n      auto name = pt.get<string>(\"firstName\") + \" \" + pt.get<string>(\"lastName\");\n\n      *response << \"HTTP/1.1 200 OK\\r\\n\"\n                << \"Content-Type: application/json\\r\\n\"\n                << \"Content-Length: \" << name.length() << \"\\r\\n\\r\\n\"\n                << name;\n    }\n    catch(const exception &e) {\n      *response << \"HTTP/1.1 400 Bad Request\\r\\nContent-Length: \" << strlen(e.what()) << \"\\r\\n\\r\\n\"\n                << e.what();\n    }\n\n\n    // Alternatively, using a convenience function:\n    // try {\n    //     ptree pt;\n    //     read_json(request->content, pt);\n\n    //     auto name=pt.get<string>(\"firstName\")+\" \"+pt.get<string>(\"lastName\");\n    //     response->write(name, {{\"Content-Type\", \"application/json\"}});\n    // }\n    // catch(const exception &e) {\n    //     response->write(SimpleWeb::StatusCode::client_error_bad_request, e.what());\n    // }\n  };\n\n  //GET-example for the path /info\n  //Responds with request-information\n  server.resource[\"^/info$\"][\"GET\"] = [](shared_ptr<HttpsServer::Response> response, shared_ptr<HttpsServer::Request> request) {\n    stringstream stream;\n    stream << \"<h1>Request from \" << request->remote_endpoint_address << \" (\" << request->remote_endpoint_port << \")</h1>\";\n    stream << request->method << \" \" << request->path << \" HTTP/\" << request->http_version << \"<br>\";\n    for(auto &header : request->header)\n      stream << header.first << \": \" << header.second << \"<br>\";\n\n    //find length of content_stream (length received using content_stream.tellp())\n    stream.seekp(0, ios::end);\n\n    *response << \"HTTP/1.1 200 OK\\r\\nContent-Length: \" << stream.tellp() << \"\\r\\n\\r\\n\"\n              << stream.rdbuf();\n\n\n    // Alternatively, using a convenience function:\n    // stringstream stream;\n    // stream << \"<h1>Request from \" << request->remote_endpoint_address << \" (\" << request->remote_endpoint_port << \")</h1>\";\n    // stream << request->method << \" \" << request->path << \" HTTP/\" << request->http_version << \"<br>\";\n    // for(auto &header: request->header)\n    //     stream << header.first << \": \" << header.second << \"<br>\";\n    // response->write(stream);\n  };\n\n  //GET-example for the path /match/[number], responds with the matched string in path (number)\n  //For instance a request GET /match/123 will receive: 123\n  server.resource[\"^/match/([0-9]+)$\"][\"GET\"] = [](shared_ptr<HttpsServer::Response> response, shared_ptr<HttpsServer::Request> request) {\n    string number = request->path_match[1];\n    *response << \"HTTP/1.1 200 OK\\r\\nContent-Length: \" << number.length() << \"\\r\\n\\r\\n\"\n              << number;\n\n\n    // Alternatively, using a convenience function:\n    // response->write(request->path_match[1]);\n  };\n\n  //Get example simulating heavy work in a separate thread\n  server.resource[\"^/work$\"][\"GET\"] = [](shared_ptr<HttpsServer::Response> response, shared_ptr<HttpsServer::Request> /*request*/) {\n    thread work_thread([response] {\n      this_thread::sleep_for(chrono::seconds(5));\n      response->write(\"Work done\");\n    });\n    work_thread.detach();\n  };\n\n  //Default GET-example. If no other matches, this anonymous function will be called.\n  //Will respond with content in the web/-directory, and its subdirectories.\n  //Default file: index.html\n  //Can for instance be used to retrieve an HTML 5 client that uses REST-resources on this server\n  server.default_resource[\"GET\"] = [&server](shared_ptr<HttpsServer::Response> response, shared_ptr<HttpsServer::Request> request) {\n    try {\n      auto web_root_path = boost::filesystem::canonical(\"web\");\n      auto path = boost::filesystem::canonical(web_root_path / request->path);\n      //Check if path is within web_root_path\n      if(distance(web_root_path.begin(), web_root_path.end()) > distance(path.begin(), path.end()) ||\n         !equal(web_root_path.begin(), web_root_path.end(), path.begin()))\n        throw invalid_argument(\"path must be within root path\");\n      if(boost::filesystem::is_directory(path))\n        path /= \"index.html\";\n\n      SimpleWeb::CaseInsensitiveMultimap header;\n\n//    Uncomment the following line to enable Cache-Control\n//    header.emplace(\"Cache-Control\", \"max-age=86400\");\n\n#ifdef HAVE_OPENSSL\n//    Uncomment the following lines to enable ETag\n//    {\n//      ifstream ifs(path.string(), ifstream::in | ios::binary);\n//      if(ifs) {\n//        auto hash = SimpleWeb::Crypto::to_hex_string(SimpleWeb::Crypto::md5(ifs));\n//        header.emplace(\"ETag\", \"\\\"\" + hash + \"\\\"\");\n//        auto it = request->header.find(\"If-None-Match\");\n//        if(it != request->header.end()) {\n//          if(!it->second.empty() && it->second.compare(1, hash.size(), hash) == 0) {\n//            response->write(SimpleWeb::StatusCode::redirection_not_modified, header);\n//            return;\n//          }\n//        }\n//      }\n//      else\n//        throw invalid_argument(\"could not read file\");\n//    }\n#endif\n\n      auto ifs = make_shared<ifstream>();\n      ifs->open(path.string(), ifstream::in | ios::binary | ios::ate);\n\n      if(*ifs) {\n        auto length = ifs->tellg();\n        ifs->seekg(0, ios::beg);\n\n        header.emplace(\"Content-Length\", to_string(length));\n        response->write(header);\n        default_resource_send(server, response, ifs);\n      }\n      else\n        throw invalid_argument(\"could not read file\");\n    }\n    catch(const exception &e) {\n      response->write(SimpleWeb::StatusCode::client_error_bad_request, \"Could not open path \" + request->path + \": \" + e.what());\n    }\n  };\n\n  server.on_error = [](std::shared_ptr<HttpsServer::Request> /*request*/, const SimpleWeb::error_code & /*ec*/) {\n    // handle errors here\n  };\n\n  thread server_thread([&server]() {\n    //Start server\n    server.start();\n  });\n\n  //Wait for server to start so that the client can connect\n  this_thread::sleep_for(chrono::seconds(1));\n\n  //Client examples\n  //Second Client() parameter set to false: no certificate verification\n  HttpsClient client(\"localhost:8080\", false);\n\n  // synchronous request examples\n  auto r1 = client.request(\"GET\", \"/match/123\");\n  cout << r1->content.rdbuf() << endl; // Alternatively, use the convenience function r1->content.string()\n\n  string json_string = \"{\\\"firstName\\\": \\\"John\\\",\\\"lastName\\\": \\\"Smith\\\",\\\"age\\\": 25}\";\n  auto r2 = client.request(\"POST\", \"/string\", json_string);\n  cout << r2->content.rdbuf() << endl;\n\n  // asynchronous request example\n  client.request(\"POST\", \"/json\", json_string, [](std::shared_ptr<HttpsClient::Response> response, const SimpleWeb::error_code &ec) {\n    if(!ec)\n      cout << response->content.rdbuf() << endl;\n  });\n  client.io_service->reset(); // needed because the io_service has been run already in the synchronous examples\n  client.io_service->run();\n\n  server_thread.join();\n}\n\nvoid default_resource_send(const HttpsServer &server, const shared_ptr<HttpsServer::Response> &response, const shared_ptr<ifstream> &ifs) {\n  //read and send 128 KB at a time\n  static vector<char> buffer(131072); // Safe when server is running on one thread\n  streamsize read_length;\n  if((read_length = ifs->read(&buffer[0], buffer.size()).gcount()) > 0) {\n    response->write(&buffer[0], read_length);\n    if(read_length == static_cast<streamsize>(buffer.size())) {\n      server.send(response, [&server, response, ifs](const SimpleWeb::error_code &ec) {\n        if(!ec)\n          default_resource_send(server, response, ifs);\n        else\n          cerr << \"Connection interrupted\" << endl;\n      });\n    }\n  }\n}\n"
  },
  {
    "path": "server_http.hpp",
    "content": "#ifndef SERVER_HTTP_HPP\n#define SERVER_HTTP_HPP\n\n#include \"utility.hpp\"\n#include <functional>\n#include <iostream>\n#include <map>\n#include <sstream>\n#include <thread>\n\n#ifdef USE_STANDALONE_ASIO\n#include <asio.hpp>\nnamespace SimpleWeb {\n  using error_code = std::error_code;\n  using errc = std::errc;\n  namespace make_error_code = std;\n} // namespace SimpleWeb\n#else\n#include <boost/asio.hpp>\nnamespace SimpleWeb {\n  namespace asio = boost::asio;\n  using error_code = boost::system::error_code;\n  namespace errc = boost::system::errc;\n  namespace make_error_code = boost::system::errc;\n} // namespace SimpleWeb\n#endif\n\n// Late 2017 TODO: remove the following checks and always use std::regex\n#ifdef USE_BOOST_REGEX\n#include <boost/regex.hpp>\nnamespace SimpleWeb {\n  namespace regex = boost;\n}\n#else\n#include <regex>\nnamespace SimpleWeb {\n  namespace regex = std;\n}\n#endif\n\nnamespace SimpleWeb {\n  template <class socket_type>\n  class Server;\n\n  template <class socket_type>\n  class ServerBase {\n  public:\n    virtual ~ServerBase() {}\n\n    class Response : public std::ostream {\n      friend class ServerBase<socket_type>;\n\n      asio::streambuf streambuf;\n\n      std::shared_ptr<socket_type> socket;\n\n      Response(const std::shared_ptr<socket_type> &socket) : std::ostream(&streambuf), socket(socket) {}\n\n      template <class size_type>\n      void write_header(const CaseInsensitiveMultimap &header, size_type size) {\n        bool content_length_written = false;\n        bool chunked_transfer_encoding = false;\n        for(auto &field : header) {\n          if(!content_length_written && case_insensitive_equal(field.first, \"content-length\"))\n            content_length_written = true;\n          else if(!chunked_transfer_encoding && case_insensitive_equal(field.first, \"transfer-encoding\") && case_insensitive_equal(field.second, \"chunked\"))\n            chunked_transfer_encoding = true;\n\n          *this << field.first << \": \" << field.second << \"\\r\\n\";\n        }\n        if(!content_length_written && !chunked_transfer_encoding && !close_connection_after_response)\n          *this << \"Content-Length: \" << size << \"\\r\\n\\r\\n\";\n        else\n          *this << \"\\r\\n\";\n      }\n\n    public:\n      size_t size() {\n        return streambuf.size();\n      }\n\n      /// Write directly to stream buffer using std::ostream::write\n      void write(const char_type *ptr, std::streamsize n) {\n        std::ostream::write(ptr, n);\n      }\n\n      /// Convenience function for writing status line, potential header fields, and empty content\n      void write(StatusCode status_code = StatusCode::success_ok, const CaseInsensitiveMultimap &header = CaseInsensitiveMultimap()) {\n        *this << \"HTTP/1.1 \" << SimpleWeb::status_code(status_code) << \"\\r\\n\";\n        write_header(header, 0);\n      }\n\n      /// Convenience function for writing status line, header fields, and content\n      void write(StatusCode status_code, const std::string &content, const CaseInsensitiveMultimap &header = CaseInsensitiveMultimap()) {\n        *this << \"HTTP/1.1 \" << SimpleWeb::status_code(status_code) << \"\\r\\n\";\n        write_header(header, content.size());\n        if(!content.empty())\n          *this << content;\n      }\n\n      /// Convenience function for writing status line, header fields, and content\n      void write(StatusCode status_code, std::istream &content, const CaseInsensitiveMultimap &header = CaseInsensitiveMultimap()) {\n        *this << \"HTTP/1.1 \" << SimpleWeb::status_code(status_code) << \"\\r\\n\";\n        content.seekg(0, std::ios::end);\n        auto size = content.tellg();\n        content.seekg(0, std::ios::beg);\n        write_header(header, size);\n        if(size)\n          *this << content.rdbuf();\n      }\n\n      /// Convenience function for writing success status line, header fields, and content\n      void write(const std::string &content, const CaseInsensitiveMultimap &header = CaseInsensitiveMultimap()) {\n        write(StatusCode::success_ok, content, header);\n      }\n\n      /// Convenience function for writing success status line, header fields, and content\n      void write(std::istream &content, const CaseInsensitiveMultimap &header = CaseInsensitiveMultimap()) {\n        write(StatusCode::success_ok, content, header);\n      }\n\n      /// Convenience function for writing success status line, and header fields\n      void write(const CaseInsensitiveMultimap &header) {\n        write(StatusCode::success_ok, std::string(), header);\n      }\n\n      /// If true, force server to close the connection after the response have been sent.\n      ///\n      /// This is useful when implementing a HTTP/1.0-server sending content\n      /// without specifying the content length.\n      bool close_connection_after_response = false;\n    };\n\n    class Content : public std::istream {\n      friend class ServerBase<socket_type>;\n\n    public:\n      size_t size() {\n        return streambuf.size();\n      }\n      /// Convenience function to return std::string. Note that the stream buffer is emptied when this functions is used.\n      std::string string() {\n        std::stringstream ss;\n        ss << rdbuf();\n        return ss.str();\n      }\n\n    private:\n      asio::streambuf &streambuf;\n      Content(asio::streambuf &streambuf) : std::istream(&streambuf), streambuf(streambuf) {}\n    };\n\n    class Request {\n      friend class ServerBase<socket_type>;\n      friend class Server<socket_type>;\n\n    public:\n      std::string method, path, http_version;\n\n      Content content;\n\n      CaseInsensitiveMultimap header;\n\n      regex::smatch path_match;\n\n      std::string remote_endpoint_address;\n      unsigned short remote_endpoint_port;\n\n      /// Returns query keys with percent-decoded values.\n      CaseInsensitiveMultimap parse_query_string() {\n        auto pos = path.find('?');\n        if(pos != std::string::npos && pos + 1 < path.size())\n          return SimpleWeb::QueryString::parse(path.substr(pos + 1));\n        else\n          return CaseInsensitiveMultimap();\n      }\n\n    private:\n      Request(const socket_type &socket) : content(streambuf) {\n        try {\n          remote_endpoint_address = socket.lowest_layer().remote_endpoint().address().to_string();\n          remote_endpoint_port = socket.lowest_layer().remote_endpoint().port();\n        }\n        catch(...) {\n        }\n      }\n\n      asio::streambuf streambuf;\n    };\n\n    class Config {\n      friend class ServerBase<socket_type>;\n\n      Config(unsigned short port) : port(port) {}\n\n    public:\n      /// Port number to use. Defaults to 80 for HTTP and 443 for HTTPS.\n      unsigned short port;\n      /// Number of threads that the server will use when start() is called. Defaults to 1 thread.\n      size_t thread_pool_size = 1;\n      /// Timeout on request handling. Defaults to 5 seconds.\n      size_t timeout_request = 5;\n      /// Timeout on content handling. Defaults to 300 seconds.\n      size_t timeout_content = 300;\n      /// IPv4 address in dotted decimal form or IPv6 address in hexadecimal notation.\n      /// If empty, the address will be any address.\n      std::string address;\n      /// Set to false to avoid binding the socket to an address that is already in use. Defaults to true.\n      bool reuse_address = true;\n    };\n    ///Set before calling start().\n    Config config;\n\n  private:\n    class regex_orderable : public regex::regex {\n      std::string str;\n\n    public:\n      regex_orderable(const char *regex_cstr) : regex::regex(regex_cstr), str(regex_cstr) {}\n      regex_orderable(const std::string &regex_str) : regex::regex(regex_str), str(regex_str) {}\n      bool operator<(const regex_orderable &rhs) const {\n        return str < rhs.str;\n      }\n    };\n\n  public:\n    /// Warning: do not add or remove resources after start() is called\n    std::map<regex_orderable, std::map<std::string, std::function<void(std::shared_ptr<typename ServerBase<socket_type>::Response>, std::shared_ptr<typename ServerBase<socket_type>::Request>)>>> resource;\n\n    std::map<std::string, std::function<void(std::shared_ptr<typename ServerBase<socket_type>::Response>, std::shared_ptr<typename ServerBase<socket_type>::Request>)>> default_resource;\n\n    std::function<void(std::shared_ptr<typename ServerBase<socket_type>::Request>, const error_code &)> on_error;\n\n    std::function<void(std::shared_ptr<socket_type> socket, std::shared_ptr<typename ServerBase<socket_type>::Request>)> on_upgrade;\n\n    virtual void start() {\n      if(!io_service)\n        io_service = std::make_shared<asio::io_service>();\n\n      if(io_service->stopped())\n        io_service->reset();\n\n      asio::ip::tcp::endpoint endpoint;\n      if(config.address.size() > 0)\n        endpoint = asio::ip::tcp::endpoint(asio::ip::address::from_string(config.address), config.port);\n      else\n        endpoint = asio::ip::tcp::endpoint(asio::ip::tcp::v4(), config.port);\n\n      if(!acceptor)\n        acceptor = std::unique_ptr<asio::ip::tcp::acceptor>(new asio::ip::tcp::acceptor(*io_service));\n      acceptor->open(endpoint.protocol());\n      acceptor->set_option(asio::socket_base::reuse_address(config.reuse_address));\n      acceptor->bind(endpoint);\n      acceptor->listen();\n\n      accept();\n\n      //If thread_pool_size>1, start m_io_service.run() in (thread_pool_size-1) threads for thread-pooling\n      threads.clear();\n      for(size_t c = 1; c < config.thread_pool_size; c++) {\n        threads.emplace_back([this]() {\n          io_service->run();\n        });\n      }\n\n      //Main thread\n      if(config.thread_pool_size > 0)\n        io_service->run();\n\n      //Wait for the rest of the threads, if any, to finish as well\n      for(auto &t : threads) {\n        t.join();\n      }\n    }\n\n    void stop() {\n      acceptor->close();\n      if(config.thread_pool_size > 0)\n        io_service->stop();\n    }\n\n    ///Use this function if you need to recursively send parts of a longer message\n    void send(const std::shared_ptr<Response> &response, const std::function<void(const error_code &)> &callback = nullptr) const {\n      asio::async_write(*response->socket, response->streambuf, [this, response, callback](const error_code &ec, size_t /*bytes_transferred*/) {\n        if(callback)\n          callback(ec);\n      });\n    }\n\n    /// If you have your own asio::io_service, store its pointer here before running start().\n    /// You might also want to set config.thread_pool_size to 0.\n    std::shared_ptr<asio::io_service> io_service;\n\n  protected:\n    std::unique_ptr<asio::ip::tcp::acceptor> acceptor;\n    std::vector<std::thread> threads;\n\n    ServerBase(unsigned short port) : config(port) {}\n\n    virtual void accept() = 0;\n\n    std::shared_ptr<asio::deadline_timer> get_timeout_timer(const std::shared_ptr<socket_type> &socket, long seconds) {\n      if(seconds == 0)\n        return nullptr;\n\n      auto timer = std::make_shared<asio::deadline_timer>(*io_service);\n      timer->expires_from_now(boost::posix_time::seconds(seconds));\n      timer->async_wait([socket](const error_code &ec) {\n        if(!ec) {\n          error_code ec;\n          socket->lowest_layer().shutdown(asio::ip::tcp::socket::shutdown_both, ec);\n          socket->lowest_layer().close();\n        }\n      });\n      return timer;\n    }\n\n    void read_request_and_content(const std::shared_ptr<socket_type> &socket) {\n      //Create new streambuf (Request::streambuf) for async_read_until()\n      //shared_ptr is used to pass temporary objects to the asynchronous functions\n      std::shared_ptr<Request> request(new Request(*socket));\n\n      //Set timeout on the following asio::async-read or write function\n      auto timer = this->get_timeout_timer(socket, config.timeout_request);\n\n      asio::async_read_until(*socket, request->streambuf, \"\\r\\n\\r\\n\", [this, socket, request, timer](const error_code &ec, size_t bytes_transferred) {\n        if(timer)\n          timer->cancel();\n        if(!ec) {\n          //request->streambuf.size() is not necessarily the same as bytes_transferred, from Boost-docs:\n          //\"After a successful async_read_until operation, the streambuf may contain additional data beyond the delimiter\"\n          //The chosen solution is to extract lines from the stream directly when parsing the header. What is left of the\n          //streambuf (maybe some bytes of the content) is appended to in the async_read-function below (for retrieving content).\n          size_t num_additional_bytes = request->streambuf.size() - bytes_transferred;\n\n          if(!this->parse_request(request))\n            return;\n\n          //If content, read that as well\n          auto it = request->header.find(\"Content-Length\");\n          if(it != request->header.end()) {\n            unsigned long long content_length;\n            try {\n              content_length = stoull(it->second);\n            }\n            catch(const std::exception &e) {\n              if(on_error)\n                on_error(request, make_error_code::make_error_code(errc::protocol_error));\n              return;\n            }\n            if(content_length > num_additional_bytes) {\n              //Set timeout on the following asio::async-read or write function\n              auto timer = this->get_timeout_timer(socket, config.timeout_content);\n              asio::async_read(*socket, request->streambuf, asio::transfer_exactly(content_length - num_additional_bytes), [this, socket, request, timer](const error_code &ec, size_t /*bytes_transferred*/) {\n                if(timer)\n                  timer->cancel();\n                if(!ec)\n                  this->find_resource(socket, request);\n                else if(on_error)\n                  on_error(request, ec);\n              });\n            }\n            else\n              this->find_resource(socket, request);\n          }\n          else\n            this->find_resource(socket, request);\n        }\n        else if(on_error)\n          on_error(request, ec);\n      });\n    }\n\n    bool parse_request(const std::shared_ptr<Request> &request) const {\n      std::string line;\n      getline(request->content, line);\n      size_t method_end;\n      if((method_end = line.find(' ')) != std::string::npos) {\n        size_t path_end;\n        if((path_end = line.find(' ', method_end + 1)) != std::string::npos) {\n          request->method = line.substr(0, method_end);\n          request->path = line.substr(method_end + 1, path_end - method_end - 1);\n\n          size_t protocol_end;\n          if((protocol_end = line.find('/', path_end + 1)) != std::string::npos) {\n            if(line.compare(path_end + 1, protocol_end - path_end - 1, \"HTTP\") != 0)\n              return false;\n            request->http_version = line.substr(protocol_end + 1, line.size() - protocol_end - 2);\n          }\n          else\n            return false;\n\n          getline(request->content, line);\n          size_t param_end;\n          while((param_end = line.find(':')) != std::string::npos) {\n            size_t value_start = param_end + 1;\n            if((value_start) < line.size()) {\n              if(line[value_start] == ' ')\n                value_start++;\n              if(value_start < line.size())\n                request->header.emplace(line.substr(0, param_end), line.substr(value_start, line.size() - value_start - 1));\n            }\n\n            getline(request->content, line);\n          }\n        }\n        else\n          return false;\n      }\n      else\n        return false;\n      return true;\n    }\n\n    void find_resource(const std::shared_ptr<socket_type> &socket, const std::shared_ptr<Request> &request) {\n      //Upgrade connection\n      if(on_upgrade) {\n        auto it = request->header.find(\"Upgrade\");\n        if(it != request->header.end()) {\n          on_upgrade(socket, request);\n          return;\n        }\n      }\n      //Find path- and method-match, and call write_response\n      for(auto &regex_method : resource) {\n        auto it = regex_method.second.find(request->method);\n        if(it != regex_method.second.end()) {\n          regex::smatch sm_res;\n          if(regex::regex_match(request->path, sm_res, regex_method.first)) {\n            request->path_match = std::move(sm_res);\n            write_response(socket, request, it->second);\n            return;\n          }\n        }\n      }\n      auto it = default_resource.find(request->method);\n      if(it != default_resource.end()) {\n        write_response(socket, request, it->second);\n      }\n    }\n\n    void write_response(const std::shared_ptr<socket_type> &socket, const std::shared_ptr<Request> &request,\n                        std::function<void(std::shared_ptr<typename ServerBase<socket_type>::Response>, std::shared_ptr<typename ServerBase<socket_type>::Request>)> &resource_function) {\n      //Set timeout on the following asio::async-read or write function\n      auto timer = this->get_timeout_timer(socket, config.timeout_content);\n\n      auto response = std::shared_ptr<Response>(new Response(socket), [this, request, timer](Response *response_ptr) {\n        auto response = std::shared_ptr<Response>(response_ptr);\n        this->send(response, [this, response, request, timer](const error_code &ec) {\n          if(timer)\n            timer->cancel();\n          if(!ec) {\n            if(response->close_connection_after_response)\n              return;\n\n            auto range = request->header.equal_range(\"Connection\");\n            for(auto it = range.first; it != range.second; it++) {\n              if(case_insensitive_equal(it->second, \"close\")) {\n                return;\n              }\n              else if(case_insensitive_equal(it->second, \"keep-alive\")) {\n                this->read_request_and_content(response->socket);\n                return;\n              }\n            }\n            if(request->http_version >= \"1.1\")\n              this->read_request_and_content(response->socket);\n          }\n          else if(on_error)\n            on_error(request, ec);\n        });\n      });\n\n      try {\n        resource_function(response, request);\n      }\n      catch(const std::exception &e) {\n        if(on_error)\n          on_error(request, make_error_code::make_error_code(errc::operation_canceled));\n        return;\n      }\n    }\n  };\n\n  template <class socket_type>\n  class Server : public ServerBase<socket_type> {};\n\n  typedef asio::ip::tcp::socket HTTP;\n\n  template <>\n  class Server<HTTP> : public ServerBase<HTTP> {\n  public:\n    DEPRECATED Server(unsigned short port, size_t thread_pool_size = 1, long timeout_request = 5, long timeout_content = 300) : Server() {\n      config.port = port;\n      config.thread_pool_size = thread_pool_size;\n      config.timeout_request = timeout_request;\n      config.timeout_content = timeout_content;\n    }\n\n    Server() : ServerBase<HTTP>::ServerBase(80) {}\n\n  protected:\n    void accept() {\n      //Create new socket for this connection\n      //Shared_ptr is used to pass temporary objects to the asynchronous functions\n      auto socket = std::make_shared<HTTP>(*io_service);\n\n      acceptor->async_accept(*socket, [this, socket](const error_code &ec) {\n        //Immediately start accepting a new connection (if io_service hasn't been stopped)\n        if(ec != asio::error::operation_aborted)\n          accept();\n\n        if(!ec) {\n          asio::ip::tcp::no_delay option(true);\n          socket->set_option(option);\n\n          this->read_request_and_content(socket);\n        }\n        else if(on_error)\n          on_error(std::shared_ptr<Request>(new Request(*socket)), ec);\n      });\n    }\n  };\n} // namespace SimpleWeb\n\n#endif /* SERVER_HTTP_HPP */\n"
  },
  {
    "path": "server_https.hpp",
    "content": "#ifndef SERVER_HTTPS_HPP\n#define SERVER_HTTPS_HPP\n\n#include \"server_http.hpp\"\n\n#ifdef USE_STANDALONE_ASIO\n#include <asio/ssl.hpp>\n#else\n#include <boost/asio/ssl.hpp>\n#endif\n\n#include <algorithm>\n#include <openssl/ssl.h>\n\nnamespace SimpleWeb {\n  typedef asio::ssl::stream<asio::ip::tcp::socket> HTTPS;\n\n  template <>\n  class Server<HTTPS> : public ServerBase<HTTPS> {\n    std::string session_id_context;\n    bool set_session_id_context = false;\n\n  public:\n    DEPRECATED Server(unsigned short port, size_t thread_pool_size, const std::string &cert_file, const std::string &private_key_file,\n                      long timeout_request = 5, long timeout_content = 300, const std::string &verify_file = std::string())\n        : Server(cert_file, private_key_file, verify_file) {\n      config.port = port;\n      config.thread_pool_size = thread_pool_size;\n      config.timeout_request = timeout_request;\n      config.timeout_content = timeout_content;\n    }\n\n    Server(const std::string &cert_file, const std::string &private_key_file, const std::string &verify_file = std::string())\n        : ServerBase<HTTPS>::ServerBase(443), context(asio::ssl::context::tlsv12) {\n      context.use_certificate_chain_file(cert_file);\n      context.use_private_key_file(private_key_file, asio::ssl::context::pem);\n\n      if(verify_file.size() > 0) {\n        context.load_verify_file(verify_file);\n        context.set_verify_mode(asio::ssl::verify_peer | asio::ssl::verify_fail_if_no_peer_cert | asio::ssl::verify_client_once);\n        set_session_id_context = true;\n      }\n    }\n\n    void start() {\n      if(set_session_id_context) {\n        // Creating session_id_context from address:port but reversed due to small SSL_MAX_SSL_SESSION_ID_LENGTH\n        session_id_context = std::to_string(config.port) + ':';\n        session_id_context.append(config.address.rbegin(), config.address.rend());\n        SSL_CTX_set_session_id_context(context.native_handle(), reinterpret_cast<const unsigned char *>(session_id_context.data()),\n                                       std::min<size_t>(session_id_context.size(), SSL_MAX_SSL_SESSION_ID_LENGTH));\n      }\n      ServerBase::start();\n    }\n\n  protected:\n    asio::ssl::context context;\n\n    void accept() {\n      //Create new socket for this connection\n      //Shared_ptr is used to pass temporary objects to the asynchronous functions\n      auto socket = std::make_shared<HTTPS>(*io_service, context);\n\n      acceptor->async_accept((*socket).lowest_layer(), [this, socket](const error_code &ec) {\n        //Immediately start accepting a new connection (if io_service hasn't been stopped)\n        if(ec != asio::error::operation_aborted)\n          accept();\n\n\n        if(!ec) {\n          asio::ip::tcp::no_delay option(true);\n          socket->lowest_layer().set_option(option);\n\n          //Set timeout on the following asio::ssl::stream::async_handshake\n          auto timer = get_timeout_timer(socket, config.timeout_request);\n          socket->async_handshake(asio::ssl::stream_base::server, [this, socket, timer](const error_code &ec) {\n            if(timer)\n              timer->cancel();\n            if(!ec)\n              read_request_and_content(socket);\n            else if(on_error)\n              on_error(std::shared_ptr<Request>(new Request(*socket)), ec);\n          });\n        }\n        else if(on_error)\n          on_error(std::shared_ptr<Request>(new Request(*socket)), ec);\n      });\n    }\n  };\n} // namespace SimpleWeb\n\n#endif /* SERVER_HTTPS_HPP */\n"
  },
  {
    "path": "status_code.hpp",
    "content": "#ifndef SIMPLE_WEB_SERVER_STATUS_CODE_HPP\n#define SIMPLE_WEB_SERVER_STATUS_CODE_HPP\n\n#include <string>\n#include <vector>\n\nnamespace SimpleWeb {\n  enum class StatusCode {\n    unknown = 0,\n    information_continue = 100,\n    information_switching_protocols,\n    information_processing,\n    success_ok = 200,\n    success_created,\n    success_accepted,\n    success_non_authoritative_information,\n    success_no_content,\n    success_reset_content,\n    success_partial_content,\n    success_multi_status,\n    success_already_reported,\n    success_im_used = 226,\n    redirection_multiple_choices = 300,\n    redirection_moved_permanently,\n    redirection_found,\n    redirection_see_other,\n    redirection_not_modified,\n    redirection_use_proxy,\n    redirection_switch_proxy,\n    redirection_temporary_redirect,\n    redirection_permanent_redirect,\n    client_error_bad_request = 400,\n    client_error_unauthorized,\n    client_error_payment_required,\n    client_error_forbidden,\n    client_error_not_found,\n    client_error_method_not_allowed,\n    client_error_not_acceptable,\n    client_error_proxy_authentication_required,\n    client_error_request_timeout,\n    client_error_conflict,\n    client_error_gone,\n    client_error_length_required,\n    client_error_precondition_failed,\n    client_error_payload_too_large,\n    client_error_uri_too_long,\n    client_error_unsupported_media_type,\n    client_error_range_not_satisfiable,\n    client_error_expectation_failed,\n    client_error_im_a_teapot,\n    client_error_misdirection_required = 421,\n    client_error_unprocessable_entity,\n    client_error_locked,\n    client_error_failed_dependency,\n    client_error_upgrade_required = 426,\n    client_error_precondition_required = 428,\n    client_error_too_many_requests,\n    client_error_request_header_fields_too_large = 431,\n    client_error_unavailable_for_legal_reasons = 451,\n    server_error_internal_server_error = 500,\n    server_error_not_implemented,\n    server_error_bad_gateway,\n    server_error_service_unavailable,\n    server_error_gateway_timeout,\n    server_error_http_version_not_supported,\n    server_error_variant_also_negotiates,\n    server_error_insufficient_storage,\n    server_error_loop_detected,\n    server_error_not_extended = 510,\n    server_error_network_authentication_required\n  };\n\n  inline const static std::vector<std::pair<StatusCode, std::string>> &status_codes() {\n    static std::vector<std::pair<StatusCode, std::string>> status_codes = {\n        {StatusCode::unknown, \"\"},\n        {StatusCode::information_continue, \"100 Continue\"},\n        {StatusCode::information_switching_protocols, \"101 Switching Protocols\"},\n        {StatusCode::information_processing, \"102 Processing\"},\n        {StatusCode::success_ok, \"200 OK\"},\n        {StatusCode::success_created, \"201 Created\"},\n        {StatusCode::success_accepted, \"202 Accepted\"},\n        {StatusCode::success_non_authoritative_information, \"203 Non-Authoritative Information\"},\n        {StatusCode::success_no_content, \"204 No Content\"},\n        {StatusCode::success_reset_content, \"205 Reset Content\"},\n        {StatusCode::success_partial_content, \"206 Partial Content\"},\n        {StatusCode::success_multi_status, \"207 Multi-Status\"},\n        {StatusCode::success_already_reported, \"208 Already Reported\"},\n        {StatusCode::success_im_used, \"226 IM Used\"},\n        {StatusCode::redirection_multiple_choices, \"300 Multiple Choices\"},\n        {StatusCode::redirection_moved_permanently, \"301 Moved Permanently\"},\n        {StatusCode::redirection_found, \"302 Found\"},\n        {StatusCode::redirection_see_other, \"303 See Other\"},\n        {StatusCode::redirection_not_modified, \"304 Not Modified\"},\n        {StatusCode::redirection_use_proxy, \"305 Use Proxy\"},\n        {StatusCode::redirection_switch_proxy, \"306 Switch Proxy\"},\n        {StatusCode::redirection_temporary_redirect, \"307 Temporary Redirect\"},\n        {StatusCode::redirection_permanent_redirect, \"308 Permanent Redirect\"},\n        {StatusCode::client_error_bad_request, \"400 Bad Request\"},\n        {StatusCode::client_error_unauthorized, \"401 Unauthorized\"},\n        {StatusCode::client_error_payment_required, \"402 Payment Required\"},\n        {StatusCode::client_error_forbidden, \"403 Forbidden\"},\n        {StatusCode::client_error_not_found, \"404 Not Found\"},\n        {StatusCode::client_error_method_not_allowed, \"405 Method Not Allowed\"},\n        {StatusCode::client_error_not_acceptable, \"406 Not Acceptable\"},\n        {StatusCode::client_error_proxy_authentication_required, \"407 Proxy Authentication Required\"},\n        {StatusCode::client_error_request_timeout, \"408 Request Timeout\"},\n        {StatusCode::client_error_conflict, \"409 Conflict\"},\n        {StatusCode::client_error_gone, \"410 Gone\"},\n        {StatusCode::client_error_length_required, \"411 Length Required\"},\n        {StatusCode::client_error_precondition_failed, \"412 Precondition Failed\"},\n        {StatusCode::client_error_payload_too_large, \"413 Payload Too Large\"},\n        {StatusCode::client_error_uri_too_long, \"414 URI Too Long\"},\n        {StatusCode::client_error_unsupported_media_type, \"415 Unsupported Media Type\"},\n        {StatusCode::client_error_range_not_satisfiable, \"416 Range Not Satisfiable\"},\n        {StatusCode::client_error_expectation_failed, \"417 Expectation Failed\"},\n        {StatusCode::client_error_im_a_teapot, \"418 I'm a teapot\"},\n        {StatusCode::client_error_misdirection_required, \"421 Misdirected Request\"},\n        {StatusCode::client_error_unprocessable_entity, \"422 Unprocessable Entity\"},\n        {StatusCode::client_error_locked, \"423 Locked\"},\n        {StatusCode::client_error_failed_dependency, \"424 Failed Dependency\"},\n        {StatusCode::client_error_upgrade_required, \"426 Upgrade Required\"},\n        {StatusCode::client_error_precondition_required, \"428 Precondition Required\"},\n        {StatusCode::client_error_too_many_requests, \"429 Too Many Requests\"},\n        {StatusCode::client_error_request_header_fields_too_large, \"431 Request Header Fields Too Large\"},\n        {StatusCode::client_error_unavailable_for_legal_reasons, \"451 Unavailable For Legal Reasons\"},\n        {StatusCode::server_error_internal_server_error, \"500 Internal Server Error\"},\n        {StatusCode::server_error_not_implemented, \"501 Not Implemented\"},\n        {StatusCode::server_error_bad_gateway, \"502 Bad Gateway\"},\n        {StatusCode::server_error_service_unavailable, \"503 Service Unavailable\"},\n        {StatusCode::server_error_gateway_timeout, \"504 Gateway Timeout\"},\n        {StatusCode::server_error_http_version_not_supported, \"505 HTTP Version Not Supported\"},\n        {StatusCode::server_error_variant_also_negotiates, \"506 Variant Also Negotiates\"},\n        {StatusCode::server_error_insufficient_storage, \"507 Insufficient Storage\"},\n        {StatusCode::server_error_loop_detected, \"508 Loop Detected\"},\n        {StatusCode::server_error_not_extended, \"510 Not Extended\"},\n        {StatusCode::server_error_network_authentication_required, \"511 Network Authentication Required\"}};\n    return status_codes;\n  }\n\n  inline StatusCode status_code(const std::string &status_code_str) {\n    for(auto &status_code : status_codes()) {\n      if(status_code.second == status_code_str)\n        return status_code.first;\n    }\n    return StatusCode::unknown;\n  }\n\n  inline const std::string &status_code(StatusCode status_code_enum) {\n    for(auto &status_code : status_codes()) {\n      if(status_code.first == status_code_enum)\n        return status_code.second;\n    }\n    return status_codes()[0].second;\n  }\n} // namespace SimpleWeb\n\n#endif // SIMPLE_WEB_SERVER_STATUS_CODE_HPP\n"
  },
  {
    "path": "tests/CMakeLists.txt",
    "content": "set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} -fno-access-control\")\n\nadd_executable(io_test io_test.cpp)\ntarget_link_libraries(io_test ${Boost_LIBRARIES})\ntarget_link_libraries(io_test ${CMAKE_THREAD_LIBS_INIT})\n\nadd_executable(parse_test parse_test.cpp)\ntarget_link_libraries(parse_test ${Boost_LIBRARIES})\ntarget_link_libraries(parse_test ${CMAKE_THREAD_LIBS_INIT})\n\nif(MSYS) #TODO: Is MSYS true when MSVC is true?\n    target_link_libraries(io_test ws2_32 wsock32)\n    target_link_libraries(parse_test ws2_32 wsock32)\nendif()\n\nadd_test(io_test io_test)\nadd_test(parse_test parse_test)\n\nif(OPENSSL_FOUND)\n    add_executable(crypto_test crypto_test.cpp)\n    target_link_libraries(crypto_test ${OPENSSL_CRYPTO_LIBRARY})\n    add_test(crypto_test crypto_test)\nendif()\n"
  },
  {
    "path": "tests/crypto_test.cpp",
    "content": "#include <cassert>\n#include <vector>\n\n#include \"crypto.hpp\"\n\nusing namespace std;\nusing namespace SimpleWeb;\n\nconst vector<pair<string, string>> base64_string_tests = {\n    {\"\", \"\"},\n    {\"f\", \"Zg==\"},\n    {\"fo\", \"Zm8=\"},\n    {\"foo\", \"Zm9v\"},\n    {\"foob\", \"Zm9vYg==\"},\n    {\"fooba\", \"Zm9vYmE=\"},\n    {\"foobar\", \"Zm9vYmFy\"}};\n\nconst vector<pair<string, string>> md5_string_tests = {\n    {\"\", \"d41d8cd98f00b204e9800998ecf8427e\"},\n    {\"The quick brown fox jumps over the lazy dog\", \"9e107d9d372bb6826bd81d3542a419d6\"}};\n\nconst vector<pair<string, string>> sha1_string_tests = {\n    {\"\", \"da39a3ee5e6b4b0d3255bfef95601890afd80709\"},\n    {\"The quick brown fox jumps over the lazy dog\", \"2fd4e1c67a2d28fced849ee1bb76e7391b93eb12\"}};\n\nconst vector<pair<string, string>> sha256_string_tests = {\n    {\"\", \"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\"},\n    {\"The quick brown fox jumps over the lazy dog\", \"d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592\"}};\n\nconst vector<pair<string, string>> sha512_string_tests = {\n    {\"\", \"cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e\"},\n    {\"The quick brown fox jumps over the lazy dog\", \"07e547d9586f6a73f73fbac0435ed76951218fb7d0c8d788a309d785436bbb642e93a252a954f23912547d1e8a3b5ed6e1bfd7097821233fa0538f3db854fee6\"}};\n\nint main() {\n  for(auto &string_test : base64_string_tests) {\n    assert(Crypto::Base64::encode(string_test.first) == string_test.second);\n    assert(Crypto::Base64::decode(string_test.second) == string_test.first);\n  }\n\n  for(auto &string_test : md5_string_tests) {\n    assert(Crypto::to_hex_string(Crypto::md5(string_test.first)) == string_test.second);\n    stringstream ss(string_test.first);\n    assert(Crypto::to_hex_string(Crypto::md5(ss)) == string_test.second);\n  }\n\n  for(auto &string_test : sha1_string_tests) {\n    assert(Crypto::to_hex_string(Crypto::sha1(string_test.first)) == string_test.second);\n    stringstream ss(string_test.first);\n    assert(Crypto::to_hex_string(Crypto::sha1(ss)) == string_test.second);\n  }\n\n  for(auto &string_test : sha256_string_tests) {\n    assert(Crypto::to_hex_string(Crypto::sha256(string_test.first)) == string_test.second);\n    stringstream ss(string_test.first);\n    assert(Crypto::to_hex_string(Crypto::sha256(ss)) == string_test.second);\n  }\n\n  for(auto &string_test : sha512_string_tests) {\n    assert(Crypto::to_hex_string(Crypto::sha512(string_test.first)) == string_test.second);\n    stringstream ss(string_test.first);\n    assert(Crypto::to_hex_string(Crypto::sha512(ss)) == string_test.second);\n  }\n\n  //Testing iterations\n  assert(Crypto::to_hex_string(Crypto::sha1(\"Test\", 1)) == \"640ab2bae07bedc4c163f679a746f7ab7fb5d1fa\");\n  assert(Crypto::to_hex_string(Crypto::sha1(\"Test\", 2)) == \"af31c6cbdecd88726d0a9b3798c71ef41f1624d5\");\n  stringstream ss(\"Test\");\n  assert(Crypto::to_hex_string(Crypto::sha1(ss, 2)) == \"af31c6cbdecd88726d0a9b3798c71ef41f1624d5\");\n\n  assert(Crypto::to_hex_string(Crypto::pbkdf2(\"Password\", \"Salt\", 4096, 128 / 8)) == \"f66df50f8aaa11e4d9721e1312ff2e66\");\n  assert(Crypto::to_hex_string(Crypto::pbkdf2(\"Password\", \"Salt\", 8192, 512 / 8)) == \"a941ccbc34d1ee8ebbd1d34824a419c3dc4eac9cbc7c36ae6c7ca8725e2b618a6ad22241e787af937b0960cf85aa8ea3a258f243e05d3cc9b08af5dd93be046c\");\n}\n"
  },
  {
    "path": "tests/io_test.cpp",
    "content": "#include \"client_http.hpp\"\n#include \"server_http.hpp\"\n\n#include <cassert>\n\nusing namespace std;\n\ntypedef SimpleWeb::Server<SimpleWeb::HTTP> HttpServer;\ntypedef SimpleWeb::Client<SimpleWeb::HTTP> HttpClient;\n\nint main() {\n  HttpServer server;\n  server.config.port = 8080;\n\n  server.resource[\"^/string$\"][\"POST\"] = [](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> request) {\n    auto content = request->content.string();\n\n    *response << \"HTTP/1.1 200 OK\\r\\nContent-Length: \" << content.length() << \"\\r\\n\\r\\n\"\n              << content;\n  };\n\n  server.resource[\"^/string2$\"][\"POST\"] = [](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> request) {\n    response->write(request->content.string());\n  };\n\n  server.resource[\"^/string3$\"][\"POST\"] = [](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> request) {\n    std::stringstream stream;\n    stream << request->content.rdbuf();\n    response->write(stream);\n  };\n\n  server.resource[\"^/string4$\"][\"POST\"] = [](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> /*request*/) {\n    response->write(SimpleWeb::StatusCode::client_error_forbidden, {{\"Test1\", \"test2\"}, {\"tesT3\", \"test4\"}});\n  };\n\n  server.resource[\"^/info$\"][\"GET\"] = [](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> request) {\n    stringstream content_stream;\n    content_stream << request->method << \" \" << request->path << \" \" << request->http_version << \" \";\n    content_stream << request->header.find(\"test parameter\")->second;\n\n    content_stream.seekp(0, ios::end);\n\n    *response << \"HTTP/1.1 200 OK\\r\\nContent-Length: \" << content_stream.tellp() << \"\\r\\n\\r\\n\"\n              << content_stream.rdbuf();\n  };\n\n  server.resource[\"^/match/([0-9]+)$\"][\"GET\"] = [&server](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> request) {\n    string number = request->path_match[1];\n    *response << \"HTTP/1.1 200 OK\\r\\nContent-Length: \" << number.length() << \"\\r\\n\\r\\n\"\n              << number;\n  };\n\n  server.resource[\"^/header$\"][\"GET\"] = [](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> request) {\n    auto content = request->header.find(\"test1\")->second + request->header.find(\"test2\")->second;\n\n    *response << \"HTTP/1.1 200 OK\\r\\nContent-Length: \" << content.length() << \"\\r\\n\\r\\n\"\n              << content;\n  };\n\n  thread server_thread([&server]() {\n    //Start server\n    server.start();\n  });\n\n  this_thread::sleep_for(chrono::seconds(1));\n  {\n    HttpClient client(\"localhost:8080\");\n\n    {\n      stringstream output;\n      auto r = client.request(\"POST\", \"/string\", \"A string\");\n      assert(SimpleWeb::status_code(r->status_code) == SimpleWeb::StatusCode::success_ok);\n      output << r->content.rdbuf();\n      assert(output.str() == \"A string\");\n    }\n\n    {\n      stringstream output;\n      auto r = client.request(\"POST\", \"/string\", \"A string\");\n      assert(SimpleWeb::status_code(r->status_code) == SimpleWeb::StatusCode::success_ok);\n      assert(r->content.string() == \"A string\");\n    }\n\n    {\n      stringstream output;\n      auto r = client.request(\"POST\", \"/string2\", \"A string\");\n      assert(SimpleWeb::status_code(r->status_code) == SimpleWeb::StatusCode::success_ok);\n      output << r->content.rdbuf();\n      assert(output.str() == \"A string\");\n    }\n\n    {\n      stringstream output;\n      auto r = client.request(\"POST\", \"/string3\", \"A string\");\n      assert(SimpleWeb::status_code(r->status_code) == SimpleWeb::StatusCode::success_ok);\n      output << r->content.rdbuf();\n      assert(output.str() == \"A string\");\n    }\n\n    {\n      stringstream output;\n      auto r = client.request(\"POST\", \"/string4\", \"A string\");\n      assert(SimpleWeb::status_code(r->status_code) == SimpleWeb::StatusCode::client_error_forbidden);\n      assert(r->header.size() == 3);\n      assert(r->header.find(\"test1\")->second == \"test2\");\n      assert(r->header.find(\"tEst3\")->second == \"test4\");\n      assert(r->header.find(\"content-length\")->second == \"0\");\n      output << r->content.rdbuf();\n      assert(output.str() == \"\");\n    }\n\n    {\n      stringstream output;\n      stringstream content(\"A string\");\n      auto r = client.request(\"POST\", \"/string\", content);\n      output << r->content.rdbuf();\n      assert(output.str() == \"A string\");\n    }\n\n    {\n      stringstream output;\n      auto r = client.request(\"GET\", \"/info\", \"\", {{\"Test Parameter\", \"test value\"}});\n      output << r->content.rdbuf();\n      assert(output.str() == \"GET /info 1.1 test value\");\n    }\n\n    {\n      stringstream output;\n      auto r = client.request(\"GET\", \"/match/123\");\n      output << r->content.rdbuf();\n      assert(output.str() == \"123\");\n    }\n  }\n  {\n    HttpClient client(\"localhost:8080\");\n\n    HttpClient::Connection *connection;\n    {\n      // test performing the stream version of the request methods first\n      stringstream output;\n      stringstream content(\"A string\");\n      auto r = client.request(\"POST\", \"/string\", content);\n      output << r->content.rdbuf();\n      assert(output.str() == \"A string\");\n      assert(client.connections->size() == 1);\n      connection = client.connections->front().get();\n    }\n\n    {\n      stringstream output;\n      auto r = client.request(\"POST\", \"/string\", \"A string\");\n      output << r->content.rdbuf();\n      assert(output.str() == \"A string\");\n      assert(client.connections->size() == 1);\n      assert(connection == client.connections->front().get());\n    }\n\n    {\n      stringstream output;\n      auto r = client.request(\"GET\", \"/header\", \"\", {{\"test1\", \"test\"}, {\"test2\", \"ing\"}});\n      output << r->content.rdbuf();\n      assert(output.str() == \"testing\");\n      assert(client.connections->size() == 1);\n      assert(connection == client.connections->front().get());\n    }\n  }\n\n  {\n    HttpClient client(\"localhost:8080\");\n    bool call = false;\n    client.request(\"GET\", \"/match/123\", [&call](shared_ptr<HttpClient::Response> response, const SimpleWeb::error_code &ec) {\n      assert(!ec);\n      stringstream output;\n      output << response->content.rdbuf();\n      assert(output.str() == \"123\");\n      call = true;\n    });\n    client.io_service->run();\n    assert(call);\n\n    {\n      vector<int> calls(100);\n      vector<thread> threads;\n      for(size_t c = 0; c < 100; ++c) {\n        calls[c] = 0;\n        threads.emplace_back([c, &client, &calls] {\n          client.request(\"GET\", \"/match/123\", [c, &calls](shared_ptr<HttpClient::Response> response, const SimpleWeb::error_code &ec) {\n            assert(!ec);\n            stringstream output;\n            output << response->content.rdbuf();\n            assert(output.str() == \"123\");\n            calls[c] = 1;\n          });\n        });\n      }\n      for(auto &thread : threads)\n        thread.join();\n      assert(client.connections->size() == 100);\n      client.io_service->reset();\n      client.io_service->run();\n      assert(client.connections->size() == 1);\n      for(auto call : calls)\n        assert(call);\n    }\n  }\n\n  {\n    HttpClient client(\"localhost:8080\");\n    assert(client.connections->size() == 0);\n    for(size_t c = 0; c < 5000; ++c) {\n      auto r1 = client.request(\"POST\", \"/string\", \"A string\");\n      assert(SimpleWeb::status_code(r1->status_code) == SimpleWeb::StatusCode::success_ok);\n      assert(r1->content.string() == \"A string\");\n      assert(client.connections->size() == 1);\n\n      stringstream content(\"A string\");\n      auto r2 = client.request(\"POST\", \"/string\", content);\n      assert(SimpleWeb::status_code(r2->status_code) == SimpleWeb::StatusCode::success_ok);\n      assert(r2->content.string() == \"A string\");\n      assert(client.connections->size() == 1);\n    }\n  }\n\n  for(size_t c = 0; c < 500; ++c) {\n    {\n      HttpClient client(\"localhost:8080\");\n      auto r = client.request(\"POST\", \"/string\", \"A string\");\n      assert(SimpleWeb::status_code(r->status_code) == SimpleWeb::StatusCode::success_ok);\n      assert(r->content.string() == \"A string\");\n      assert(client.connections->size() == 1);\n    }\n\n    {\n      HttpClient client(\"localhost:8080\");\n      stringstream content(\"A string\");\n      auto r = client.request(\"POST\", \"/string\", content);\n      assert(SimpleWeb::status_code(r->status_code) == SimpleWeb::StatusCode::success_ok);\n      assert(r->content.string() == \"A string\");\n      assert(client.connections->size() == 1);\n    }\n  }\n\n  server.stop();\n  server_thread.join();\n\n  return 0;\n}\n"
  },
  {
    "path": "tests/parse_test.cpp",
    "content": "#include \"client_http.hpp\"\n#include \"server_http.hpp\"\n#include <cassert>\n#include <iostream>\n\nusing namespace std;\nusing namespace SimpleWeb;\n\nclass ServerTest : public ServerBase<HTTP> {\npublic:\n  ServerTest() : ServerBase<HTTP>::ServerBase(8080) {}\n\n  void accept() {}\n\n  void parse_request_test() {\n    HTTP socket(*io_service);\n    std::shared_ptr<Request> request(new Request(socket));\n\n    std::ostream stream(&request->content.streambuf);\n    stream << \"GET /test/ HTTP/1.1\\r\\n\";\n    stream << \"TestHeader: test\\r\\n\";\n    stream << \"TestHeader2:test2\\r\\n\";\n    stream << \"TestHeader3:test3a\\r\\n\";\n    stream << \"TestHeader3:test3b\\r\\n\";\n    stream << \"\\r\\n\";\n\n    assert(parse_request(request));\n\n    assert(request->method == \"GET\");\n    assert(request->path == \"/test/\");\n    assert(request->http_version == \"1.1\");\n\n    assert(request->header.size() == 4);\n    auto header_it = request->header.find(\"TestHeader\");\n    assert(header_it != request->header.end() && header_it->second == \"test\");\n    header_it = request->header.find(\"TestHeader2\");\n    assert(header_it != request->header.end() && header_it->second == \"test2\");\n\n    header_it = request->header.find(\"testheader\");\n    assert(header_it != request->header.end() && header_it->second == \"test\");\n    header_it = request->header.find(\"testheader2\");\n    assert(header_it != request->header.end() && header_it->second == \"test2\");\n\n    auto range = request->header.equal_range(\"testheader3\");\n    auto first = range.first;\n    auto second = first;\n    ++second;\n    assert(range.first != request->header.end() && range.second != request->header.end() &&\n           ((first->second == \"test3a\" && second->second == \"test3b\") ||\n            (first->second == \"test3b\" && second->second == \"test3a\")));\n  }\n};\n\nclass ClientTest : public ClientBase<HTTP> {\npublic:\n  ClientTest(const std::string &server_port_path) : ClientBase<HTTP>::ClientBase(server_port_path, 80) {}\n\n  std::shared_ptr<Connection> create_connection() override {\n    return nullptr;\n  }\n\n  void constructor_parse_test1() {\n    assert(host == \"test.org\");\n    assert(port == 8080);\n  }\n\n  void constructor_parse_test2() {\n    assert(host == \"test.org\");\n    assert(port == 80);\n  }\n\n  void parse_response_header_test() {\n    std::shared_ptr<Response> response(new Response());\n\n    ostream stream(&response->content_buffer);\n    stream << \"HTTP/1.1 200 OK\\r\\n\";\n    stream << \"TestHeader: test\\r\\n\";\n    stream << \"TestHeader2:test2\\r\\n\";\n    stream << \"TestHeader3:test3a\\r\\n\";\n    stream << \"TestHeader3:test3b\\r\\n\";\n    stream << \"\\r\\n\";\n\n    parse_response_header(response);\n\n    assert(response->http_version == \"1.1\");\n    assert(response->status_code == \"200 OK\");\n\n    assert(response->header.size() == 4);\n    auto header_it = response->header.find(\"TestHeader\");\n    assert(header_it != response->header.end() && header_it->second == \"test\");\n    header_it = response->header.find(\"TestHeader2\");\n    assert(header_it != response->header.end() && header_it->second == \"test2\");\n\n    header_it = response->header.find(\"testheader\");\n    assert(header_it != response->header.end() && header_it->second == \"test\");\n    header_it = response->header.find(\"testheader2\");\n    assert(header_it != response->header.end() && header_it->second == \"test2\");\n\n    auto range = response->header.equal_range(\"testheader3\");\n    auto first = range.first;\n    auto second = first;\n    ++second;\n    assert(range.first != response->header.end() && range.second != response->header.end() &&\n           ((first->second == \"test3a\" && second->second == \"test3b\") ||\n            (first->second == \"test3b\" && second->second == \"test3a\")));\n  }\n};\n\nint main() {\n  assert(case_insensitive_equal(\"Test\", \"tesT\"));\n  assert(case_insensitive_equal(\"tesT\", \"test\"));\n  assert(!case_insensitive_equal(\"test\", \"tseT\"));\n  CaseInsensitiveEqual equal;\n  assert(equal(\"Test\", \"tesT\"));\n  assert(equal(\"tesT\", \"test\"));\n  assert(!equal(\"test\", \"tset\"));\n  CaseInsensitiveHash hash;\n  assert(hash(\"Test\") == hash(\"tesT\"));\n  assert(hash(\"tesT\") == hash(\"test\"));\n  assert(hash(\"test\") != hash(\"tset\"));\n\n  auto percent_decoded = \"testing æøå !#$&'()*+,/:;=?@[]\";\n  auto percent_encoded = \"testing+æøå+%21%23%24%26%27%28%29%2A%2B%2C%2F%3A%3B%3D%3F%40%5B%5D\";\n  assert(Percent::encode(percent_decoded) == percent_encoded);\n  assert(Percent::decode(percent_encoded) == percent_decoded);\n  assert(Percent::decode(Percent::encode(percent_decoded)) == percent_decoded);\n\n  SimpleWeb::CaseInsensitiveMultimap fields = {{\"test1\", \"æøå\"}, {\"test2\", \"!#$&'()*+,/:;=?@[]\"}};\n  auto query_string1 = \"test1=æøå&test2=%21%23%24%26%27%28%29%2A%2B%2C%2F%3A%3B%3D%3F%40%5B%5D\";\n  auto query_string2 = \"test2=%21%23%24%26%27%28%29%2A%2B%2C%2F%3A%3B%3D%3F%40%5B%5D&test1=æøå\";\n  auto query_string_result = QueryString::create(fields);\n  assert(query_string_result == query_string1 || query_string_result == query_string2);\n  auto fields_result1 = QueryString::parse(query_string1);\n  auto fields_result2 = QueryString::parse(query_string2);\n  assert(fields_result1 == fields_result2 && fields_result1 == fields);\n\n  ServerTest serverTest;\n  serverTest.io_service = std::make_shared<asio::io_service>();\n\n  serverTest.parse_request_test();\n\n  ClientTest clientTest(\"test.org:8080\");\n  clientTest.constructor_parse_test1();\n\n  ClientTest clientTest2(\"test.org\");\n  clientTest2.constructor_parse_test2();\n\n  clientTest2.parse_response_header_test();\n\n\n  asio::io_service io_service;\n  asio::ip::tcp::socket socket(io_service);\n  SimpleWeb::Server<HTTP>::Request request(socket);\n  {\n    request.path = \"/?\";\n    auto queries = request.parse_query_string();\n    assert(queries.empty());\n  }\n  {\n    request.path = \"/\";\n    auto queries = request.parse_query_string();\n    assert(queries.empty());\n  }\n  {\n    request.path = \"/?=\";\n    auto queries = request.parse_query_string();\n    assert(queries.empty());\n  }\n  {\n    request.path = \"/?=test\";\n    auto queries = request.parse_query_string();\n    assert(queries.empty());\n  }\n  {\n    request.path = \"/?a=1%202%20%203&b=3+4&c&d=æ%25ø%26å%3F\";\n    auto queries = request.parse_query_string();\n    {\n      auto range = queries.equal_range(\"a\");\n      assert(range.first != range.second);\n      assert(range.first->second == \"1 2  3\");\n    }\n    {\n      auto range = queries.equal_range(\"b\");\n      assert(range.first != range.second);\n      assert(range.first->second == \"3 4\");\n    }\n    {\n      auto range = queries.equal_range(\"c\");\n      assert(range.first != range.second);\n      assert(range.first->second == \"\");\n    }\n    {\n      auto range = queries.equal_range(\"d\");\n      assert(range.first != range.second);\n      assert(range.first->second == \"æ%ø&å?\");\n    }\n  }\n}\n"
  },
  {
    "path": "utility.hpp",
    "content": "#ifndef SIMPLE_WEB_SERVER_UTILITY_HPP\n#define SIMPLE_WEB_SERVER_UTILITY_HPP\n\n#include \"status_code.hpp\"\n#include <iostream>\n#include <string>\n#include <unordered_map>\n\n// TODO when switching to c++14, use [[deprecated]] instead\n#ifndef DEPRECATED\n#ifdef __GNUC__\n#define DEPRECATED __attribute__((deprecated))\n#elif defined(_MSC_VER)\n#define DEPRECATED __declspec(deprecated)\n#else\n#define DEPRECATED\n#endif\n#endif\n\nnamespace SimpleWeb {\n#ifndef CASE_INSENSITIVE_EQUAL_AND_HASH\n#define CASE_INSENSITIVE_EQUAL_AND_HASH\n  inline bool case_insensitive_equal(const std::string &str1, const std::string &str2) {\n    return str1.size() == str2.size() &&\n           std::equal(str1.begin(), str1.end(), str2.begin(), [](char a, char b) {\n             return tolower(a) == tolower(b);\n           });\n  }\n  class CaseInsensitiveEqual {\n  public:\n    bool operator()(const std::string &str1, const std::string &str2) const {\n      return case_insensitive_equal(str1, str2);\n    }\n  };\n  // Based on https://stackoverflow.com/questions/2590677/how-do-i-combine-hash-values-in-c0x/2595226#2595226\n  class CaseInsensitiveHash {\n  public:\n    size_t operator()(const std::string &str) const {\n      size_t h = 0;\n      std::hash<int> hash;\n      for(auto c : str)\n        h ^= hash(tolower(c)) + 0x9e3779b9 + (h << 6) + (h >> 2);\n      return h;\n    }\n  };\n#endif\n\n  typedef std::unordered_multimap<std::string, std::string, CaseInsensitiveHash, CaseInsensitiveEqual> CaseInsensitiveMultimap;\n\n  /// Percent encoding and decoding\n  class Percent {\n  public:\n    /// Returns percent-encoded string\n    static std::string encode(const std::string &value) {\n      static auto hex_chars = \"0123456789ABCDEF\";\n\n      std::string result;\n      result.reserve(value.size()); // minimum size of result\n\n      for(auto &chr : value) {\n        if(chr == ' ')\n          result += '+';\n        else if(chr == '!' || chr == '#' || chr == '$' || (chr >= '&' && chr <= ',') || (chr >= '/' && chr <= ';') || chr == '=' || chr == '?' || chr == '@' || chr == '[' || chr == ']')\n          result += std::string(\"%\") + hex_chars[chr >> 4] + hex_chars[chr & 15];\n        else\n          result += chr;\n      }\n\n      return result;\n    }\n\n    /// Returns percent-decoded string\n    static std::string decode(const std::string &value) {\n      std::string result;\n      result.reserve(value.size() / 3 + (value.size() % 3)); // minimum size of result\n\n      for(size_t i = 0; i < value.size(); ++i) {\n        auto &chr = value[i];\n        if(chr == '%' && i + 2 < value.size()) {\n          auto hex = value.substr(i + 1, 2);\n          auto decoded_chr = static_cast<char>(std::strtol(hex.c_str(), nullptr, 16));\n          result += decoded_chr;\n          i += 2;\n        }\n        else if(chr == '+')\n          result += ' ';\n        else\n          result += chr;\n      }\n\n      return result;\n    }\n  };\n\n  /// Query string creation and parsing\n  class QueryString {\n  public:\n    /// Returns query string created from given field names and values\n    static std::string create(const CaseInsensitiveMultimap &fields) {\n      std::string result;\n\n      bool first = true;\n      for(auto &field : fields) {\n        result += (!first ? \"&\" : \"\") + field.first + '=' + Percent::encode(field.second);\n        first = false;\n      }\n\n      return result;\n    }\n\n    /// Returns query keys with percent-decoded values.\n    static CaseInsensitiveMultimap parse(const std::string &query_string) {\n      CaseInsensitiveMultimap result;\n\n      if(query_string.empty())\n        return result;\n\n      size_t name_pos = 0;\n      size_t name_end_pos = -1;\n      size_t value_pos = -1;\n      for(size_t c = 0; c < query_string.size(); ++c) {\n        if(query_string[c] == '&') {\n          auto name = query_string.substr(name_pos, (name_end_pos == std::string::npos ? c : name_end_pos) - name_pos);\n          if(!name.empty()) {\n            auto value = value_pos == std::string::npos ? std::string() : query_string.substr(value_pos, c - value_pos);\n            result.emplace(std::move(name), Percent::decode(value));\n          }\n          name_pos = c + 1;\n          name_end_pos = -1;\n          value_pos = -1;\n        }\n        else if(query_string[c] == '=') {\n          name_end_pos = c;\n          value_pos = c + 1;\n        }\n      }\n      if(name_pos < query_string.size()) {\n        auto name = query_string.substr(name_pos, name_end_pos - name_pos);\n        if(!name.empty()) {\n          auto value = value_pos >= query_string.size() ? std::string() : query_string.substr(value_pos);\n          result.emplace(std::move(name), Percent::decode(value));\n        }\n      }\n\n      return result;\n    }\n  };\n\n  /// Returns query keys with percent-decoded values.\n  DEPRECATED inline CaseInsensitiveMultimap parse_query_string(const std::string &query_string) {\n    return QueryString::parse(query_string);\n  }\n} // namespace SimpleWeb\n\n#endif // SIMPLE_WEB_SERVER_UTILITY_HPP\n"
  },
  {
    "path": "web/index.html",
    "content": "<html>\n    <head>\n        <title>Simple-Web-Server html-file</title>\n    </head>\n    <body>\n        This is the content of index.html\n    </body>\n</html>\n"
  },
  {
    "path": "web/test.html",
    "content": "<html>\n    <head>\n        <title>Simple-Web-Server html-file</title>\n    </head>\n    <body>\n        This is the content of test.html\n    </body>\n</html>\n"
  }
]