forked from mirror/openmw-tes3mp
[Master] Backport SimpleWeb
parent
510e657c93
commit
4e93905350
@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2014-2016 Ole Christian Eidheim
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
@ -1,55 +1,42 @@
|
|||||||
/*
|
#pragma once
|
||||||
* https://github.com/eidheim/Simple-Web-Server/
|
|
||||||
*
|
|
||||||
* The MIT License (MIT)
|
|
||||||
* Copyright (c) 2014-2016 Ole Christian Eidheim
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef SERVER_HTTP_HPP
|
|
||||||
#define SERVER_HTTP_HPP
|
|
||||||
|
|
||||||
#include "base_server.hpp"
|
#include "base_server.hpp"
|
||||||
|
|
||||||
namespace SimpleWeb
|
namespace SimpleWeb {
|
||||||
{
|
|
||||||
|
|
||||||
template <class socket_type>
|
template <class socket_type>
|
||||||
class Server : public ServerBase<socket_type> {};
|
class Server : public ServerBase<socket_type> {};
|
||||||
|
|
||||||
typedef boost::asio::ip::tcp::socket HTTP;
|
using HTTP = asio::ip::tcp::socket;
|
||||||
|
|
||||||
template <>
|
template <>
|
||||||
class Server<HTTP> : public ServerBase<HTTP>
|
class Server<HTTP> : public ServerBase<HTTP> {
|
||||||
{
|
|
||||||
public:
|
public:
|
||||||
Server() : ServerBase<HTTP>::ServerBase(80)
|
Server() noexcept : ServerBase<HTTP>::ServerBase(80) {}
|
||||||
{}
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual void accept()
|
void accept() override {
|
||||||
{
|
auto session = std::make_shared<Session>(create_connection(*io_service));
|
||||||
//Create new socket for this connection
|
|
||||||
//Shared_ptr is used to pass temporary objects to the asynchronous functions
|
acceptor->async_accept(*session->connection->socket, [this, session](const error_code &ec) {
|
||||||
auto socket = std::make_shared<HTTP>(*io_service);
|
auto lock = session->connection->handler_runner->continue_lock();
|
||||||
|
if(!lock)
|
||||||
acceptor->async_accept(*socket, [this, socket](const boost::system::error_code &ec)
|
return;
|
||||||
{
|
|
||||||
//Immediately start accepting a new connection (if io_service hasn't been stopped)
|
// Immediately start accepting a new connection (unless io_service has been stopped)
|
||||||
if (ec != boost::asio::error::operation_aborted)
|
if(ec != asio::error::operation_aborted)
|
||||||
accept();
|
this->accept();
|
||||||
|
|
||||||
if (!ec)
|
if(!ec) {
|
||||||
{
|
asio::ip::tcp::no_delay option(true);
|
||||||
boost::asio::ip::tcp::no_delay option(true);
|
error_code ec;
|
||||||
socket->set_option(option);
|
session->connection->socket->set_option(option, ec);
|
||||||
|
|
||||||
this->read_request_and_content(socket);
|
this->read_request_and_content(session);
|
||||||
}
|
}
|
||||||
else if (on_error)
|
else if(this->on_error)
|
||||||
on_error(std::shared_ptr<Request>(new Request(*socket)), ec);
|
this->on_error(session->request, ec);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
} // namespace SimpleWeb
|
||||||
|
|
||||||
#endif //SERVER_HTTP_HPP
|
|
||||||
|
@ -1,91 +1,82 @@
|
|||||||
#ifndef HTTPS_SERVER_HPP
|
#pragma once
|
||||||
#define HTTPS_SERVER_HPP
|
|
||||||
|
|
||||||
#include "base_server.hpp"
|
#include "base_server.hpp"
|
||||||
|
|
||||||
|
#ifdef USE_STANDALONE_ASIO
|
||||||
|
#include <asio/ssl.hpp>
|
||||||
|
#else
|
||||||
#include <boost/asio/ssl.hpp>
|
#include <boost/asio/ssl.hpp>
|
||||||
#include <openssl/ssl.h>
|
#endif
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <openssl/ssl.h>
|
||||||
|
|
||||||
namespace SimpleWeb
|
namespace SimpleWeb {
|
||||||
{
|
using HTTPS = asio::ssl::stream<asio::ip::tcp::socket>;
|
||||||
typedef boost::asio::ssl::stream<boost::asio::ip::tcp::socket> HTTPS;
|
|
||||||
|
|
||||||
template <>
|
template <>
|
||||||
class Server<HTTPS> : public ServerBase<HTTPS>
|
class Server<HTTPS> : public ServerBase<HTTPS> {
|
||||||
{
|
|
||||||
std::string session_id_context;
|
std::string session_id_context;
|
||||||
bool set_session_id_context = false;
|
bool set_session_id_context = false;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Server(const std::string &cert_file, const std::string &private_key_file,
|
Server(const std::string &cert_file, const std::string &private_key_file, const std::string &verify_file = std::string())
|
||||||
const std::string &verify_file = std::string()) : ServerBase<HTTPS>::ServerBase(443),
|
: ServerBase<HTTPS>::ServerBase(443), context(asio::ssl::context::tlsv12) {
|
||||||
context(boost::asio::ssl::context::tlsv12)
|
|
||||||
{
|
|
||||||
context.use_certificate_chain_file(cert_file);
|
context.use_certificate_chain_file(cert_file);
|
||||||
context.use_private_key_file(private_key_file, boost::asio::ssl::context::pem);
|
context.use_private_key_file(private_key_file, asio::ssl::context::pem);
|
||||||
|
|
||||||
if (verify_file.size() > 0)
|
if(verify_file.size() > 0) {
|
||||||
{
|
|
||||||
context.load_verify_file(verify_file);
|
context.load_verify_file(verify_file);
|
||||||
context.set_verify_mode(boost::asio::ssl::verify_peer | boost::asio::ssl::verify_fail_if_no_peer_cert |
|
context.set_verify_mode(asio::ssl::verify_peer | asio::ssl::verify_fail_if_no_peer_cert | asio::ssl::verify_client_once);
|
||||||
boost::asio::ssl::verify_client_once);
|
|
||||||
set_session_id_context = true;
|
set_session_id_context = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void start()
|
void start() override {
|
||||||
{
|
if(set_session_id_context) {
|
||||||
if (set_session_id_context)
|
|
||||||
{
|
|
||||||
// Creating session_id_context from address:port but reversed due to small SSL_MAX_SSL_SESSION_ID_LENGTH
|
// Creating session_id_context from address:port but reversed due to small SSL_MAX_SSL_SESSION_ID_LENGTH
|
||||||
session_id_context = std::to_string(config.port) + ':';
|
session_id_context = std::to_string(config.port) + ':';
|
||||||
session_id_context.append(config.address.rbegin(), config.address.rend());
|
session_id_context.append(config.address.rbegin(), config.address.rend());
|
||||||
SSL_CTX_set_session_id_context(context.native_handle(),
|
SSL_CTX_set_session_id_context(context.native_handle(), reinterpret_cast<const unsigned char *>(session_id_context.data()),
|
||||||
reinterpret_cast<const unsigned char *>(session_id_context.data()),
|
std::min<size_t>(session_id_context.size(), SSL_MAX_SSL_SESSION_ID_LENGTH));
|
||||||
std::min<size_t>(session_id_context.size(),
|
|
||||||
SSL_MAX_SSL_SESSION_ID_LENGTH));
|
|
||||||
}
|
}
|
||||||
ServerBase::start();
|
ServerBase::start();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
boost::asio::ssl::context context;
|
asio::ssl::context context;
|
||||||
|
|
||||||
virtual void accept()
|
void accept() override {
|
||||||
{
|
auto session = std::make_shared<Session>(create_connection(*io_service, context));
|
||||||
//Create new socket for this connection
|
|
||||||
//Shared_ptr is used to pass temporary objects to the asynchronous functions
|
|
||||||
auto socket = std::make_shared<HTTPS>(*io_service, context);
|
|
||||||
|
|
||||||
acceptor->async_accept((*socket).lowest_layer(), [this, socket](const boost::system::error_code &ec)
|
acceptor->async_accept(session->connection->socket->lowest_layer(), [this, session](const error_code &ec) {
|
||||||
{
|
auto lock = session->connection->handler_runner->continue_lock();
|
||||||
//Immediately start accepting a new connection (if io_service hasn't been stopped)
|
if(!lock)
|
||||||
if (ec != boost::asio::error::operation_aborted)
|
return;
|
||||||
accept();
|
|
||||||
|
|
||||||
|
if(ec != asio::error::operation_aborted)
|
||||||
|
this->accept();
|
||||||
|
|
||||||
if (!ec)
|
if(!ec) {
|
||||||
{
|
asio::ip::tcp::no_delay option(true);
|
||||||
boost::asio::ip::tcp::no_delay option(true);
|
error_code ec;
|
||||||
socket->lowest_layer().set_option(option);
|
session->connection->socket->lowest_layer().set_option(option, ec);
|
||||||
|
|
||||||
//Set timeout on the following boost::asio::ssl::stream::async_handshake
|
session->connection->set_timeout(config.timeout_request);
|
||||||
auto timer = get_timeout_timer(socket, config.timeout_request);
|
session->connection->socket->async_handshake(asio::ssl::stream_base::server, [this, session](const error_code &ec) {
|
||||||
socket->async_handshake(boost::asio::ssl::stream_base::server, [this, socket, timer]
|
session->connection->cancel_timeout();
|
||||||
(const boost::system::error_code &ec)
|
auto lock = session->connection->handler_runner->continue_lock();
|
||||||
{
|
if(!lock)
|
||||||
if (timer)
|
return;
|
||||||
timer->cancel();
|
|
||||||
if(!ec)
|
if(!ec)
|
||||||
read_request_and_content(socket);
|
this->read_request_and_content(session);
|
||||||
else if (on_error)
|
else if(this->on_error)
|
||||||
on_error(std::shared_ptr<Request>(new Request(*socket)), ec);
|
this->on_error(session->request, ec);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else if (on_error)
|
else if(this->on_error)
|
||||||
on_error(std::shared_ptr<Request>(new Request(*socket)), ec);
|
this->on_error(session->request, ec);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
} // namespace SimpleWeb
|
||||||
|
|
||||||
#endif //HTTPS_SERVER_HPP
|
|
||||||
|
@ -0,0 +1,154 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace SimpleWeb {
|
||||||
|
enum class StatusCode {
|
||||||
|
unknown = 0,
|
||||||
|
information_continue = 100,
|
||||||
|
information_switching_protocols,
|
||||||
|
information_processing,
|
||||||
|
success_ok = 200,
|
||||||
|
success_created,
|
||||||
|
success_accepted,
|
||||||
|
success_non_authoritative_information,
|
||||||
|
success_no_content,
|
||||||
|
success_reset_content,
|
||||||
|
success_partial_content,
|
||||||
|
success_multi_status,
|
||||||
|
success_already_reported,
|
||||||
|
success_im_used = 226,
|
||||||
|
redirection_multiple_choices = 300,
|
||||||
|
redirection_moved_permanently,
|
||||||
|
redirection_found,
|
||||||
|
redirection_see_other,
|
||||||
|
redirection_not_modified,
|
||||||
|
redirection_use_proxy,
|
||||||
|
redirection_switch_proxy,
|
||||||
|
redirection_temporary_redirect,
|
||||||
|
redirection_permanent_redirect,
|
||||||
|
client_error_bad_request = 400,
|
||||||
|
client_error_unauthorized,
|
||||||
|
client_error_payment_required,
|
||||||
|
client_error_forbidden,
|
||||||
|
client_error_not_found,
|
||||||
|
client_error_method_not_allowed,
|
||||||
|
client_error_not_acceptable,
|
||||||
|
client_error_proxy_authentication_required,
|
||||||
|
client_error_request_timeout,
|
||||||
|
client_error_conflict,
|
||||||
|
client_error_gone,
|
||||||
|
client_error_length_required,
|
||||||
|
client_error_precondition_failed,
|
||||||
|
client_error_payload_too_large,
|
||||||
|
client_error_uri_too_long,
|
||||||
|
client_error_unsupported_media_type,
|
||||||
|
client_error_range_not_satisfiable,
|
||||||
|
client_error_expectation_failed,
|
||||||
|
client_error_im_a_teapot,
|
||||||
|
client_error_misdirection_required = 421,
|
||||||
|
client_error_unprocessable_entity,
|
||||||
|
client_error_locked,
|
||||||
|
client_error_failed_dependency,
|
||||||
|
client_error_upgrade_required = 426,
|
||||||
|
client_error_precondition_required = 428,
|
||||||
|
client_error_too_many_requests,
|
||||||
|
client_error_request_header_fields_too_large = 431,
|
||||||
|
client_error_unavailable_for_legal_reasons = 451,
|
||||||
|
server_error_internal_server_error = 500,
|
||||||
|
server_error_not_implemented,
|
||||||
|
server_error_bad_gateway,
|
||||||
|
server_error_service_unavailable,
|
||||||
|
server_error_gateway_timeout,
|
||||||
|
server_error_http_version_not_supported,
|
||||||
|
server_error_variant_also_negotiates,
|
||||||
|
server_error_insufficient_storage,
|
||||||
|
server_error_loop_detected,
|
||||||
|
server_error_not_extended = 510,
|
||||||
|
server_error_network_authentication_required
|
||||||
|
};
|
||||||
|
|
||||||
|
const static std::vector<std::pair<StatusCode, std::string>> &status_codes() noexcept {
|
||||||
|
const static std::vector<std::pair<StatusCode, std::string>> status_codes = {
|
||||||
|
{StatusCode::unknown, ""},
|
||||||
|
{StatusCode::information_continue, "100 Continue"},
|
||||||
|
{StatusCode::information_switching_protocols, "101 Switching Protocols"},
|
||||||
|
{StatusCode::information_processing, "102 Processing"},
|
||||||
|
{StatusCode::success_ok, "200 OK"},
|
||||||
|
{StatusCode::success_created, "201 Created"},
|
||||||
|
{StatusCode::success_accepted, "202 Accepted"},
|
||||||
|
{StatusCode::success_non_authoritative_information, "203 Non-Authoritative Information"},
|
||||||
|
{StatusCode::success_no_content, "204 No Content"},
|
||||||
|
{StatusCode::success_reset_content, "205 Reset Content"},
|
||||||
|
{StatusCode::success_partial_content, "206 Partial Content"},
|
||||||
|
{StatusCode::success_multi_status, "207 Multi-Status"},
|
||||||
|
{StatusCode::success_already_reported, "208 Already Reported"},
|
||||||
|
{StatusCode::success_im_used, "226 IM Used"},
|
||||||
|
{StatusCode::redirection_multiple_choices, "300 Multiple Choices"},
|
||||||
|
{StatusCode::redirection_moved_permanently, "301 Moved Permanently"},
|
||||||
|
{StatusCode::redirection_found, "302 Found"},
|
||||||
|
{StatusCode::redirection_see_other, "303 See Other"},
|
||||||
|
{StatusCode::redirection_not_modified, "304 Not Modified"},
|
||||||
|
{StatusCode::redirection_use_proxy, "305 Use Proxy"},
|
||||||
|
{StatusCode::redirection_switch_proxy, "306 Switch Proxy"},
|
||||||
|
{StatusCode::redirection_temporary_redirect, "307 Temporary Redirect"},
|
||||||
|
{StatusCode::redirection_permanent_redirect, "308 Permanent Redirect"},
|
||||||
|
{StatusCode::client_error_bad_request, "400 Bad Request"},
|
||||||
|
{StatusCode::client_error_unauthorized, "401 Unauthorized"},
|
||||||
|
{StatusCode::client_error_payment_required, "402 Payment Required"},
|
||||||
|
{StatusCode::client_error_forbidden, "403 Forbidden"},
|
||||||
|
{StatusCode::client_error_not_found, "404 Not Found"},
|
||||||
|
{StatusCode::client_error_method_not_allowed, "405 Method Not Allowed"},
|
||||||
|
{StatusCode::client_error_not_acceptable, "406 Not Acceptable"},
|
||||||
|
{StatusCode::client_error_proxy_authentication_required, "407 Proxy Authentication Required"},
|
||||||
|
{StatusCode::client_error_request_timeout, "408 Request Timeout"},
|
||||||
|
{StatusCode::client_error_conflict, "409 Conflict"},
|
||||||
|
{StatusCode::client_error_gone, "410 Gone"},
|
||||||
|
{StatusCode::client_error_length_required, "411 Length Required"},
|
||||||
|
{StatusCode::client_error_precondition_failed, "412 Precondition Failed"},
|
||||||
|
{StatusCode::client_error_payload_too_large, "413 Payload Too Large"},
|
||||||
|
{StatusCode::client_error_uri_too_long, "414 URI Too Long"},
|
||||||
|
{StatusCode::client_error_unsupported_media_type, "415 Unsupported Media Type"},
|
||||||
|
{StatusCode::client_error_range_not_satisfiable, "416 Range Not Satisfiable"},
|
||||||
|
{StatusCode::client_error_expectation_failed, "417 Expectation Failed"},
|
||||||
|
{StatusCode::client_error_im_a_teapot, "418 I'm a teapot"},
|
||||||
|
{StatusCode::client_error_misdirection_required, "421 Misdirected Request"},
|
||||||
|
{StatusCode::client_error_unprocessable_entity, "422 Unprocessable Entity"},
|
||||||
|
{StatusCode::client_error_locked, "423 Locked"},
|
||||||
|
{StatusCode::client_error_failed_dependency, "424 Failed Dependency"},
|
||||||
|
{StatusCode::client_error_upgrade_required, "426 Upgrade Required"},
|
||||||
|
{StatusCode::client_error_precondition_required, "428 Precondition Required"},
|
||||||
|
{StatusCode::client_error_too_many_requests, "429 Too Many Requests"},
|
||||||
|
{StatusCode::client_error_request_header_fields_too_large, "431 Request Header Fields Too Large"},
|
||||||
|
{StatusCode::client_error_unavailable_for_legal_reasons, "451 Unavailable For Legal Reasons"},
|
||||||
|
{StatusCode::server_error_internal_server_error, "500 Internal Server Error"},
|
||||||
|
{StatusCode::server_error_not_implemented, "501 Not Implemented"},
|
||||||
|
{StatusCode::server_error_bad_gateway, "502 Bad Gateway"},
|
||||||
|
{StatusCode::server_error_service_unavailable, "503 Service Unavailable"},
|
||||||
|
{StatusCode::server_error_gateway_timeout, "504 Gateway Timeout"},
|
||||||
|
{StatusCode::server_error_http_version_not_supported, "505 HTTP Version Not Supported"},
|
||||||
|
{StatusCode::server_error_variant_also_negotiates, "506 Variant Also Negotiates"},
|
||||||
|
{StatusCode::server_error_insufficient_storage, "507 Insufficient Storage"},
|
||||||
|
{StatusCode::server_error_loop_detected, "508 Loop Detected"},
|
||||||
|
{StatusCode::server_error_not_extended, "510 Not Extended"},
|
||||||
|
{StatusCode::server_error_network_authentication_required, "511 Network Authentication Required"}};
|
||||||
|
return status_codes;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline StatusCode status_code(const std::string &status_code_str) noexcept {
|
||||||
|
for(auto &status_code : status_codes()) {
|
||||||
|
if(status_code.second == status_code_str)
|
||||||
|
return status_code.first;
|
||||||
|
}
|
||||||
|
return StatusCode::unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline const std::string &status_code(StatusCode status_code_enum) noexcept {
|
||||||
|
for(auto &status_code : status_codes()) {
|
||||||
|
if(status_code.first == status_code_enum)
|
||||||
|
return status_code.second;
|
||||||
|
}
|
||||||
|
return status_codes()[0].second;
|
||||||
|
}
|
||||||
|
} // namespace SimpleWeb
|
@ -0,0 +1,340 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "status_code.hpp"
|
||||||
|
#include <atomic>
|
||||||
|
#include <iostream>
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
namespace SimpleWeb {
|
||||||
|
inline bool case_insensitive_equal(const std::string &str1, const std::string &str2) noexcept {
|
||||||
|
return str1.size() == str2.size() &&
|
||||||
|
std::equal(str1.begin(), str1.end(), str2.begin(), [](char a, char b) {
|
||||||
|
return tolower(a) == tolower(b);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
class CaseInsensitiveEqual {
|
||||||
|
public:
|
||||||
|
bool operator()(const std::string &str1, const std::string &str2) const noexcept {
|
||||||
|
return case_insensitive_equal(str1, str2);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// Based on https://stackoverflow.com/questions/2590677/how-do-i-combine-hash-values-in-c0x/2595226#2595226
|
||||||
|
class CaseInsensitiveHash {
|
||||||
|
public:
|
||||||
|
size_t operator()(const std::string &str) const noexcept {
|
||||||
|
size_t h = 0;
|
||||||
|
std::hash<int> hash;
|
||||||
|
for(auto c : str)
|
||||||
|
h ^= hash(tolower(c)) + 0x9e3779b9 + (h << 6) + (h >> 2);
|
||||||
|
return h;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
using CaseInsensitiveMultimap = std::unordered_multimap<std::string, std::string, CaseInsensitiveHash, CaseInsensitiveEqual>;
|
||||||
|
|
||||||
|
/// Percent encoding and decoding
|
||||||
|
class Percent {
|
||||||
|
public:
|
||||||
|
/// Returns percent-encoded string
|
||||||
|
static std::string encode(const std::string &value) noexcept {
|
||||||
|
static auto hex_chars = "0123456789ABCDEF";
|
||||||
|
|
||||||
|
std::string result;
|
||||||
|
result.reserve(value.size()); // Minimum size of result
|
||||||
|
|
||||||
|
for(auto &chr : value) {
|
||||||
|
if(chr == ' ')
|
||||||
|
result += '+';
|
||||||
|
else if(chr == '!' || chr == '#' || chr == '$' || (chr >= '&' && chr <= ',') || (chr >= '/' && chr <= ';') || chr == '=' || chr == '?' || chr == '@' || chr == '[' || chr == ']')
|
||||||
|
result += std::string("%") + hex_chars[chr >> 4] + hex_chars[chr & 15];
|
||||||
|
else
|
||||||
|
result += chr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns percent-decoded string
|
||||||
|
static std::string decode(const std::string &value) noexcept {
|
||||||
|
std::string result;
|
||||||
|
result.reserve(value.size() / 3 + (value.size() % 3)); // Minimum size of result
|
||||||
|
|
||||||
|
for(size_t i = 0; i < value.size(); ++i) {
|
||||||
|
auto &chr = value[i];
|
||||||
|
if(chr == '%' && i + 2 < value.size()) {
|
||||||
|
auto hex = value.substr(i + 1, 2);
|
||||||
|
auto decoded_chr = static_cast<char>(std::strtol(hex.c_str(), nullptr, 16));
|
||||||
|
result += decoded_chr;
|
||||||
|
i += 2;
|
||||||
|
}
|
||||||
|
else if(chr == '+')
|
||||||
|
result += ' ';
|
||||||
|
else
|
||||||
|
result += chr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Query string creation and parsing
|
||||||
|
class QueryString {
|
||||||
|
public:
|
||||||
|
/// Returns query string created from given field names and values
|
||||||
|
static std::string create(const CaseInsensitiveMultimap &fields) noexcept {
|
||||||
|
std::string result;
|
||||||
|
|
||||||
|
bool first = true;
|
||||||
|
for(auto &field : fields) {
|
||||||
|
result += (!first ? "&" : "") + field.first + '=' + Percent::encode(field.second);
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns query keys with percent-decoded values.
|
||||||
|
static CaseInsensitiveMultimap parse(const std::string &query_string) noexcept {
|
||||||
|
CaseInsensitiveMultimap result;
|
||||||
|
|
||||||
|
if(query_string.empty())
|
||||||
|
return result;
|
||||||
|
|
||||||
|
size_t name_pos = 0;
|
||||||
|
auto name_end_pos = std::string::npos;
|
||||||
|
auto value_pos = std::string::npos;
|
||||||
|
for(size_t c = 0; c < query_string.size(); ++c) {
|
||||||
|
if(query_string[c] == '&') {
|
||||||
|
auto name = query_string.substr(name_pos, (name_end_pos == std::string::npos ? c : name_end_pos) - name_pos);
|
||||||
|
if(!name.empty()) {
|
||||||
|
auto value = value_pos == std::string::npos ? std::string() : query_string.substr(value_pos, c - value_pos);
|
||||||
|
result.emplace(std::move(name), Percent::decode(value));
|
||||||
|
}
|
||||||
|
name_pos = c + 1;
|
||||||
|
name_end_pos = std::string::npos;
|
||||||
|
value_pos = std::string::npos;
|
||||||
|
}
|
||||||
|
else if(query_string[c] == '=') {
|
||||||
|
name_end_pos = c;
|
||||||
|
value_pos = c + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(name_pos < query_string.size()) {
|
||||||
|
auto name = query_string.substr(name_pos, name_end_pos - name_pos);
|
||||||
|
if(!name.empty()) {
|
||||||
|
auto value = value_pos >= query_string.size() ? std::string() : query_string.substr(value_pos);
|
||||||
|
result.emplace(std::move(name), Percent::decode(value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class HttpHeader {
|
||||||
|
public:
|
||||||
|
/// Parse header fields
|
||||||
|
static CaseInsensitiveMultimap parse(std::istream &stream) noexcept {
|
||||||
|
CaseInsensitiveMultimap result;
|
||||||
|
std::string line;
|
||||||
|
getline(stream, line);
|
||||||
|
size_t param_end;
|
||||||
|
while((param_end = line.find(':')) != std::string::npos) {
|
||||||
|
size_t value_start = param_end + 1;
|
||||||
|
if(value_start < line.size()) {
|
||||||
|
if(line[value_start] == ' ')
|
||||||
|
value_start++;
|
||||||
|
if(value_start < line.size())
|
||||||
|
result.emplace(line.substr(0, param_end), line.substr(value_start, line.size() - value_start - 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
getline(stream, line);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class RequestMessage {
|
||||||
|
public:
|
||||||
|
/// Parse request line and header fields
|
||||||
|
static bool parse(std::istream &stream, std::string &method, std::string &path, std::string &query_string, std::string &version, CaseInsensitiveMultimap &header) noexcept {
|
||||||
|
header.clear();
|
||||||
|
std::string line;
|
||||||
|
getline(stream, line);
|
||||||
|
size_t method_end;
|
||||||
|
if((method_end = line.find(' ')) != std::string::npos) {
|
||||||
|
method = line.substr(0, method_end);
|
||||||
|
|
||||||
|
size_t query_start = std::string::npos;
|
||||||
|
size_t path_and_query_string_end = std::string::npos;
|
||||||
|
for(size_t i = method_end + 1; i < line.size(); ++i) {
|
||||||
|
if(line[i] == '?' && (i + 1) < line.size())
|
||||||
|
query_start = i + 1;
|
||||||
|
else if(line[i] == ' ') {
|
||||||
|
path_and_query_string_end = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(path_and_query_string_end != std::string::npos) {
|
||||||
|
if(query_start != std::string::npos) {
|
||||||
|
path = line.substr(method_end + 1, query_start - method_end - 2);
|
||||||
|
query_string = line.substr(query_start, path_and_query_string_end - query_start);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
path = line.substr(method_end + 1, path_and_query_string_end - method_end - 1);
|
||||||
|
|
||||||
|
size_t protocol_end;
|
||||||
|
if((protocol_end = line.find('/', path_and_query_string_end + 1)) != std::string::npos) {
|
||||||
|
if(line.compare(path_and_query_string_end + 1, protocol_end - path_and_query_string_end - 1, "HTTP") != 0)
|
||||||
|
return false;
|
||||||
|
version = line.substr(protocol_end + 1, line.size() - protocol_end - 2);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
|
||||||
|
header = HttpHeader::parse(stream);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class ResponseMessage {
|
||||||
|
public:
|
||||||
|
/// Parse status line and header fields
|
||||||
|
static bool parse(std::istream &stream, std::string &version, std::string &status_code, CaseInsensitiveMultimap &header) noexcept {
|
||||||
|
header.clear();
|
||||||
|
std::string line;
|
||||||
|
getline(stream, line);
|
||||||
|
size_t version_end = line.find(' ');
|
||||||
|
if(version_end != std::string::npos) {
|
||||||
|
if(5 < line.size())
|
||||||
|
version = line.substr(5, version_end - 5);
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
if((version_end + 1) < line.size())
|
||||||
|
status_code = line.substr(version_end + 1, line.size() - (version_end + 1) - 1);
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
|
||||||
|
header = HttpHeader::parse(stream);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class ContentDisposition {
|
||||||
|
public:
|
||||||
|
/// Can be used to parse the Content-Disposition header field value when
|
||||||
|
/// clients are posting requests with enctype="multipart/form-data"
|
||||||
|
static CaseInsensitiveMultimap parse(const std::string &line) {
|
||||||
|
CaseInsensitiveMultimap result;
|
||||||
|
|
||||||
|
size_t para_start_pos = 0;
|
||||||
|
size_t para_end_pos = std::string::npos;
|
||||||
|
size_t value_start_pos = std::string::npos;
|
||||||
|
for(size_t c = 0; c < line.size(); ++c) {
|
||||||
|
if(para_start_pos != std::string::npos) {
|
||||||
|
if(para_end_pos == std::string::npos) {
|
||||||
|
if(line[c] == ';') {
|
||||||
|
result.emplace(line.substr(para_start_pos, c - para_start_pos), std::string());
|
||||||
|
para_start_pos = std::string::npos;
|
||||||
|
}
|
||||||
|
else if(line[c] == '=')
|
||||||
|
para_end_pos = c;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if(value_start_pos == std::string::npos) {
|
||||||
|
if(line[c] == '"' && c + 1 < line.size())
|
||||||
|
value_start_pos = c + 1;
|
||||||
|
}
|
||||||
|
else if(line[c] == '"') {
|
||||||
|
result.emplace(line.substr(para_start_pos, para_end_pos - para_start_pos), line.substr(value_start_pos, c - value_start_pos));
|
||||||
|
para_start_pos = std::string::npos;
|
||||||
|
para_end_pos = std::string::npos;
|
||||||
|
value_start_pos = std::string::npos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(line[c] != ' ' && line[c] != ';')
|
||||||
|
para_start_pos = c;
|
||||||
|
}
|
||||||
|
if(para_start_pos != std::string::npos && para_end_pos == std::string::npos)
|
||||||
|
result.emplace(line.substr(para_start_pos), std::string());
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} // namespace SimpleWeb
|
||||||
|
|
||||||
|
#ifdef __SSE2__
|
||||||
|
#include <emmintrin.h>
|
||||||
|
namespace SimpleWeb {
|
||||||
|
inline void spin_loop_pause() noexcept { _mm_pause(); }
|
||||||
|
} // namespace SimpleWeb
|
||||||
|
// TODO: need verification that the following checks are correct:
|
||||||
|
#elif defined(_MSC_VER) && _MSC_VER >= 1800 && (defined(_M_X64) || defined(_M_IX86))
|
||||||
|
#include <intrin.h>
|
||||||
|
namespace SimpleWeb {
|
||||||
|
inline void spin_loop_pause() noexcept { _mm_pause(); }
|
||||||
|
} // namespace SimpleWeb
|
||||||
|
#else
|
||||||
|
namespace SimpleWeb {
|
||||||
|
inline void spin_loop_pause() noexcept {}
|
||||||
|
} // namespace SimpleWeb
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace SimpleWeb {
|
||||||
|
/// Makes it possible to for instance cancel Asio handlers without stopping asio::io_service
|
||||||
|
class ScopeRunner {
|
||||||
|
/// Scope count that is set to -1 if scopes are to be canceled
|
||||||
|
std::atomic<long> count;
|
||||||
|
|
||||||
|
public:
|
||||||
|
class SharedLock {
|
||||||
|
friend class ScopeRunner;
|
||||||
|
std::atomic<long> &count;
|
||||||
|
SharedLock(std::atomic<long> &count) noexcept : count(count) {}
|
||||||
|
SharedLock &operator=(const SharedLock &) = delete;
|
||||||
|
SharedLock(const SharedLock &) = delete;
|
||||||
|
|
||||||
|
public:
|
||||||
|
~SharedLock() noexcept {
|
||||||
|
count.fetch_sub(1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ScopeRunner() noexcept : count(0) {}
|
||||||
|
|
||||||
|
/// Returns nullptr if scope should be exited, or a shared lock otherwise
|
||||||
|
std::unique_ptr<SharedLock> continue_lock() noexcept {
|
||||||
|
long expected = count;
|
||||||
|
while(expected >= 0 && !count.compare_exchange_weak(expected, expected + 1))
|
||||||
|
spin_loop_pause();
|
||||||
|
|
||||||
|
if(expected < 0)
|
||||||
|
return nullptr;
|
||||||
|
else
|
||||||
|
return std::unique_ptr<SharedLock>(new SharedLock(count));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Blocks until all shared locks are released, then prevents future shared locks
|
||||||
|
void stop() noexcept {
|
||||||
|
long expected = 0;
|
||||||
|
while(!count.compare_exchange_weak(expected, -1)) {
|
||||||
|
if(expected < 0)
|
||||||
|
return;
|
||||||
|
expected = 0;
|
||||||
|
spin_loop_pause();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} // namespace SimpleWeb
|
Loading…
Reference in New Issue