You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
283 lines
12 KiB
283 lines
12 KiB
// SPDX-License-Identifier: BSL-1.0
|
|
/*
|
|
* Digital Voice Modem - Common Library
|
|
* BSL-1.0 Open Source. Use is subject to license terms.
|
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
|
*
|
|
* Copyright (c) 2003-2013 Christopher M. Kohlhoff
|
|
* Copyright (C) 2024 Bryan Biedenkapp, N2PLL
|
|
*
|
|
*/
|
|
/**
|
|
* @file SecureServerConnection.h
|
|
* @ingroup http
|
|
*/
|
|
#if !defined(__REST_HTTP__SECURE_SERVER_CONNECTION_H__)
|
|
#define __REST_HTTP__SECURE_SERVER_CONNECTION_H__
|
|
|
|
#if defined(ENABLE_SSL)
|
|
|
|
#include "common/Defines.h"
|
|
#include "common/restapi/http/HTTPLexer.h"
|
|
#include "common/restapi/http/HTTPPayload.h"
|
|
#include "common/Log.h"
|
|
|
|
#include <array>
|
|
#include <memory>
|
|
#include <utility>
|
|
#include <iterator>
|
|
|
|
#include <asio.hpp>
|
|
#include <asio/ssl.hpp>
|
|
|
|
namespace restapi
|
|
{
|
|
namespace http
|
|
{
|
|
// ---------------------------------------------------------------------------
|
|
// Class Prototypes
|
|
// ---------------------------------------------------------------------------
|
|
|
|
template<class> class ServerConnectionManager;
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Class Declaration
|
|
// ---------------------------------------------------------------------------
|
|
|
|
/**
|
|
* @brief This class represents a single connection from a client.
|
|
* @tparam RequestHandlerType Type representing a request handler.
|
|
* @ingroup http
|
|
*/
|
|
template <typename RequestHandlerType>
|
|
class SecureServerConnection : public std::enable_shared_from_this<SecureServerConnection<RequestHandlerType>> {
|
|
typedef SecureServerConnection<RequestHandlerType> selfType;
|
|
typedef std::shared_ptr<selfType> selfTypePtr;
|
|
typedef ServerConnectionManager<selfTypePtr> ConnectionManagerType;
|
|
public:
|
|
auto operator=(SecureServerConnection&) -> SecureServerConnection& = delete;
|
|
auto operator=(SecureServerConnection&&) -> SecureServerConnection& = delete;
|
|
SecureServerConnection(SecureServerConnection&) = delete;
|
|
|
|
/**
|
|
* @brief Initializes a new instance of the SecureServerConnection class.
|
|
* @param socket TCP socket for this connection.
|
|
* @param context SSL context.
|
|
* @param manager Connection manager for this connection.
|
|
* @param handler Request handler for this connection.
|
|
* @param persistent Flag indicating whether or not the connection is persistent.
|
|
* @param debug Flag indicating whether or not verbose logging should be enabled.
|
|
*/
|
|
explicit SecureServerConnection(asio::ip::tcp::socket socket, asio::ssl::context& context, ConnectionManagerType& manager, RequestHandlerType& handler,
|
|
bool persistent = false, bool debug = false) :
|
|
m_socket(std::move(socket), context),
|
|
m_connectionManager(manager),
|
|
m_requestHandler(handler),
|
|
m_lexer(HTTPLexer(false)),
|
|
m_continue(false),
|
|
m_contResult(HTTPLexer::INDETERMINATE),
|
|
m_persistent(persistent),
|
|
m_debug(debug)
|
|
{
|
|
/* stub */
|
|
}
|
|
|
|
/**
|
|
* @brief Start the first asynchronous operation for the connection.
|
|
*/
|
|
void start() { handshake(); }
|
|
/**
|
|
* @brief Stop all asynchronous operations associated with the connection.
|
|
*/
|
|
void stop()
|
|
{
|
|
try
|
|
{
|
|
if (m_socket.lowest_layer().is_open()) {
|
|
m_socket.lowest_layer().close();
|
|
}
|
|
}
|
|
catch(const std::exception&) { /* ignore */ }
|
|
}
|
|
|
|
private:
|
|
/**
|
|
* @brief Perform an asynchronous SSL handshake.
|
|
*/
|
|
void handshake()
|
|
{
|
|
auto self = this->shared_from_this();
|
|
|
|
m_socket.async_handshake(asio::ssl::stream_base::server, [this, self](asio::error_code ec) {
|
|
if (!ec) {
|
|
read();
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* @brief Perform an asynchronous read operation.
|
|
*/
|
|
void read()
|
|
{
|
|
auto self = this->shared_from_this();
|
|
m_socket.async_read_some(asio::buffer(m_buffer), [this, self](asio::error_code ec, std::size_t recvLength) {
|
|
if (!ec) {
|
|
HTTPLexer::ResultType result = HTTPLexer::GOOD;
|
|
char* content;
|
|
|
|
// catch exceptions here so we don't blatently crash the system
|
|
try
|
|
{
|
|
if (!m_continue) {
|
|
std::tie(result, content) = m_lexer.parse(m_request, m_buffer.data(), m_buffer.data() + recvLength);
|
|
|
|
m_request.content = std::string();
|
|
std::string contentLength = m_request.headers.find("Content-Length");
|
|
if (contentLength != "" && (::strlen(content) != 0)) {
|
|
size_t length = (size_t)::strtoul(contentLength.c_str(), NULL, 10);
|
|
m_request.contentLength = length;
|
|
m_request.content = std::string(content, length);
|
|
}
|
|
|
|
m_request.headers.add("RemoteHost", m_socket.lowest_layer().remote_endpoint().address().to_string());
|
|
|
|
uint32_t consumed = m_lexer.consumed();
|
|
if (result == HTTPLexer::GOOD && consumed == recvLength &&
|
|
((m_request.method == HTTP_POST) || (m_request.method == HTTP_PUT))) {
|
|
if (m_debug) {
|
|
LogDebug(LOG_REST, "HTTPS Partial Request, recvLength = %u, consumed = %u, result = %u", recvLength, consumed, result);
|
|
Utils::dump(1U, "SecureServerConnection::read(), m_buffer", (uint8_t*)m_buffer.data(), recvLength);
|
|
}
|
|
|
|
result = HTTPLexer::INDETERMINATE;
|
|
m_continue = true;
|
|
}
|
|
} else {
|
|
if (m_debug) {
|
|
LogDebug(LOG_REST, "HTTP Partial Request, recvLength = %u, result = %u", recvLength, result);
|
|
Utils::dump(1U, "SecureServerConnection::read(), m_buffer", (uint8_t*)m_buffer.data(), recvLength);
|
|
}
|
|
|
|
if (m_contResult == HTTPLexer::INDETERMINATE) {
|
|
m_request.content = std::string(m_buffer.data(), recvLength);
|
|
} else {
|
|
m_request.content.append(std::string(m_buffer.data(), recvLength));
|
|
}
|
|
|
|
if (m_request.contentLength != 0 && recvLength < m_request.contentLength) {
|
|
m_contResult = result = HTTPLexer::CONTINUE;
|
|
m_continue = true;
|
|
}
|
|
}
|
|
|
|
if (result == HTTPLexer::GOOD) {
|
|
if (m_debug) {
|
|
Utils::dump(1U, "SecureServerConnection::read(), HTTPS Request Content", (uint8_t*)m_request.content.c_str(), m_request.content.length());
|
|
}
|
|
|
|
m_continue = false;
|
|
m_contResult = HTTPLexer::INDETERMINATE;
|
|
m_requestHandler.handleRequest(m_request, m_reply);
|
|
|
|
if (m_debug) {
|
|
Utils::dump(1U, "SecureServerConnection::read(), HTTPS Reply Content", (uint8_t*)m_reply.content.c_str(), m_reply.content.length());
|
|
}
|
|
|
|
write();
|
|
}
|
|
else if (result == HTTPLexer::BAD) {
|
|
m_continue = false;
|
|
m_contResult = HTTPLexer::INDETERMINATE;
|
|
m_reply = HTTPPayload::statusPayload(HTTPPayload::BAD_REQUEST);
|
|
write();
|
|
}
|
|
else {
|
|
read();
|
|
}
|
|
}
|
|
catch(const std::exception& e) {
|
|
::LogError(LOG_REST, "SecureServerConnection::read(), %s %s", e.what(), ec.message().c_str());
|
|
m_continue = false;
|
|
m_contResult = HTTPLexer::INDETERMINATE;
|
|
|
|
m_reply = HTTPPayload::statusPayload(HTTPPayload::INTERNAL_SERVER_ERROR);
|
|
write();
|
|
}
|
|
}
|
|
else if (ec != asio::error::operation_aborted) {
|
|
if (ec) {
|
|
::LogError(LOG_REST, "SecureServerConnection::read(), %s, code = %u", ec.message().c_str(), ec.value());
|
|
}
|
|
m_connectionManager.stop(self);
|
|
m_continue = false;
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* @brief Perform an asynchronous write operation.
|
|
*/
|
|
void write()
|
|
{
|
|
auto self = this->shared_from_this();
|
|
|
|
if (m_persistent) {
|
|
m_reply.headers.add("Connection", "keep-alive");
|
|
}
|
|
|
|
auto buffers = m_reply.toBuffers();
|
|
asio::async_write(m_socket, buffers, [this, self](asio::error_code ec, std::size_t) {
|
|
if (m_persistent) {
|
|
m_lexer.reset();
|
|
m_reply.headers = HTTPHeaders();
|
|
m_reply.status = HTTPPayload::OK;
|
|
m_reply.content = "";
|
|
m_request = HTTPPayload();
|
|
read();
|
|
}
|
|
else {
|
|
if (!ec) {
|
|
try
|
|
{
|
|
// initiate graceful connection closure
|
|
asio::error_code ignored_ec;
|
|
m_socket.lowest_layer().shutdown(asio::ip::tcp::socket::shutdown_both, ignored_ec);
|
|
}
|
|
catch(const std::exception& e) { ::LogError(LOG_REST, "%s", ec.message().c_str()); }
|
|
}
|
|
|
|
if (ec != asio::error::operation_aborted) {
|
|
if (ec) {
|
|
::LogError(LOG_REST, "SecureServerConnection::write(), %s, code = %u", ec.message().c_str(), ec.value());
|
|
}
|
|
m_connectionManager.stop(self);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
asio::ssl::stream<asio::ip::tcp::socket> m_socket;
|
|
|
|
ConnectionManagerType& m_connectionManager;
|
|
RequestHandlerType& m_requestHandler;
|
|
|
|
std::array<char, 8192> m_buffer;
|
|
|
|
HTTPPayload m_request;
|
|
HTTPLexer m_lexer;
|
|
HTTPPayload m_reply;
|
|
|
|
bool m_continue;
|
|
HTTPLexer::ResultType m_contResult;
|
|
|
|
bool m_persistent;
|
|
bool m_debug;
|
|
};
|
|
} // namespace http
|
|
} // namespace restapi
|
|
|
|
#endif // ENABLE_SSL
|
|
|
|
#endif // __REST_HTTP__SECURE_SERVER_CONNECTION_H__
|