add initial support code for RESTful RCON replacement;

pull/19/head
Bryan Biedenkapp 3 years ago
parent 7d1ae452dc
commit d3f5d00e5a

@ -102,6 +102,10 @@ file(GLOB dvmhost_SRC
"network/*.h" "network/*.h"
"network/*.cpp" "network/*.cpp"
"network/json/*.h" "network/json/*.h"
"network/rest/*.h"
"network/rest/*.cpp"
"network/rest/http/*.h"
"network/rest/http/*.cpp"
"remote/RemoteCommand.cpp" "remote/RemoteCommand.cpp"
"remote/RemoteCommand.h" "remote/RemoteCommand.h"
"yaml/*.h" "yaml/*.h"
@ -294,6 +298,12 @@ else ()
message(CHECK_PASS "no") message(CHECK_PASS "no")
endif (CROSS_COMPILE_RPI_ARM) endif (CROSS_COMPILE_RPI_ARM)
option(WITH_ASIO "Specifies the location for the ASIO library" off)
if (WITH_ASIO)
set(ASIO_INCLUDE_DIR ${WITH_ASIO}/include)
message(CHECK_START "With ASIO: ${ASIO_INCLUDE_DIR}")
endif (WITH_ASIO)
# #
# standard CMake options # standard CMake options
# #
@ -321,6 +331,14 @@ add_definitions(-D__GIT_VER_HASH__="${GIT_VER_HASH}")
# dvmhost project # dvmhost project
project(dvmhost) project(dvmhost)
find_package(Threads REQUIRED) find_package(Threads REQUIRED)
if (NOT WITH_ASIO)
find_package(ASIO REQUIRED)
else()
add_library(asio::asio INTERFACE IMPORTED)
target_include_directories(asio::asio INTERFACE ${ASIO_INCLUDE_DIR})
target_compile_definitions(asio::asio INTERFACE "ASIO_STANDALONE")
target_link_libraries(asio::asio INTERFACE Threads::Threads)
endif ()
add_executable(dvmhost ${dvmhost_SRC}) add_executable(dvmhost ${dvmhost_SRC})
target_include_directories(dvmhost PRIVATE .) target_include_directories(dvmhost PRIVATE .)
target_link_libraries(dvmhost PRIVATE Threads::Threads util) target_link_libraries(dvmhost PRIVATE Threads::Threads util)
@ -354,6 +372,7 @@ target_include_directories(dvmcmd PRIVATE .)
if (ENABLE_TESTS) if (ENABLE_TESTS)
# dvmtest project # dvmtest project
project(dvmtest) project(dvmtest)
set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake")
Include(FetchContent) Include(FetchContent)
FetchContent_Declare( FetchContent_Declare(
@ -364,6 +383,14 @@ FetchContent_Declare(
FetchContent_MakeAvailable(Catch2) FetchContent_MakeAvailable(Catch2)
find_package(Threads REQUIRED) find_package(Threads REQUIRED)
if (NOT WITH_ASIO)
find_package(ASIO REQUIRED)
else()
add_library(asio::asio INTERFACE IMPORTED)
target_include_directories(asio::asio INTERFACE ${ASIO_INCLUDE_DIR})
target_compile_definitions(asio::asio INTERFACE "ASIO_STANDALONE")
target_link_libraries(asio::asio INTERFACE Threads::Threads)
endif ()
add_executable(dvmtests ${dvmhost_SRC} ${dvmtests_SRC}) add_executable(dvmtests ${dvmhost_SRC} ${dvmtests_SRC})
target_compile_definitions(dvmtests PUBLIC -DCATCH2_TEST_COMPILATION) target_compile_definitions(dvmtests PUBLIC -DCATCH2_TEST_COMPILATION)
target_link_libraries(dvmtests PRIVATE Catch2::Catch2WithMain Threads::Threads util) target_link_libraries(dvmtests PRIVATE Catch2::Catch2WithMain Threads::Threads util)

@ -13,6 +13,13 @@ This project utilizes CMake for its build system. (All following information ass
The DVM Host software does not have any specific library dependancies and is written to be as library-free as possible. A basic GCC/G++ install is usually all thats needed to compile. The DVM Host software does not have any specific library dependancies and is written to be as library-free as possible. A basic GCC/G++ install is usually all thats needed to compile.
## Dependancies
This project requires the ASIO library (https://think-async.com/Asio/) for its REST API services. This can be installed on most Debian/Ubuntu Linux's with: ```apt-get install libasio-dev```
Alternatively, if you download the ASIO library from the ASIO website and extract it to a location, you can specify the path to the ASIO library using: ```-DWITH_ASIO=/path/to/asio```. This method is required when cross-compiling
for old Raspberry Pi ARM 32 bit.
### Build Instructions ### Build Instructions
1. Clone the repository. ```git clone https://github.com/DVMProject/dvmhost.git``` 1. Clone the repository. ```git clone https://github.com/DVMProject/dvmhost.git```
2. Switch into the "dvmhost" folder. Create a new folder named "build" and switch into it. 2. Switch into the "dvmhost" folder. Create a new folder named "build" and switch into it.

@ -0,0 +1,40 @@
#
# Finds the ASIO library.
#
# from https://think-async.com/Asio/
#
# This will define the following variables
#
# ASIO_FOUND
# ASIO_INCLUDE_DIR
#
# and the following imported targets
#
# asio::asio
#
find_package(Threads QUIET)
if (Threads_FOUND)
find_path(ASIO_INCLUDE_DIR asio.hpp)
mark_as_advanced(ASIO_FOUND ASIO_INCLUDE_DIR)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(ASIO
FOUND_VAR ASIO_FOUND
REQUIRED_VARS ASIO_INCLUDE_DIR
)
if(ASIO_FOUND AND NOT TARGET asio::asio)
add_library(asio::asio INTERFACE IMPORTED)
target_include_directories(asio::asio INTERFACE ${ASIO_INCLUDE_DIR})
target_compile_definitions(asio::asio INTERFACE "ASIO_STANDALONE")
target_link_libraries(asio::asio INTERFACE Threads::Threads)
endif()
else()
if(asio_FIND_REQUIRED)
message(FATAL_ERROR "asio requires Threads, which couldn't be found.")
elseif(asio_FIND_QUIETLY)
message(STATUS "asio requires Threads, which couldn't be found.")
endif()
endif()

@ -0,0 +1,177 @@
/**
* Digital Voice Modem - Host Software
* GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* @package DVM / Host Software
*
*/
//
// Based on code from the CRUD project. (https://github.com/venediktov/CRUD)
// Licensed under the BPL-1.0 License (https://opensource.org/license/bsl1-0-html)
//
/*
* Copyright (c) 2003-2013 Christopher M. Kohlhoff
* Copyright (C) 2023 by Bryan Biedenkapp N2PLL
*
* Permission is hereby granted, free of charge, to any person or organization
* obtaining a copy of the software and accompanying documentation covered by
* this license (the Software) to use, reproduce, display, distribute, execute,
* and transmit the Software, and to prepare derivative works of the Software, and
* to permit third-parties to whom the Software is furnished to do so, all subject
* to the following:
*
* The copyright notices in the Software and this entire statement, including the
* above license grant, this restriction and the following disclaimer, must be included
* in all copies of the Software, in whole or in part, and all derivative works of the
* Software, unless such copies or derivative works are solely in the form of
* machine-executable object code generated by a source language processor.
*
* 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, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE
* DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#if !defined(__REST__DISPATHER_H__)
#define __REST__DISPATHER_H__
#include "Defines.h"
#include <functional>
#include <map>
#include <string>
#include <regex>
#include <memory>
namespace rest {
// ---------------------------------------------------------------------------
// Structure Declaration
//
// ---------------------------------------------------------------------------
template<typename Matched>
struct RequestMatch : Matched
{
/// <summary>Initializes a new instance of the RequestMatch structure.</summary>
RequestMatch(const Matched& m, const std::string& d) : Matched(m) , data(d) { /* stub */ }
std::string data;
};
// ---------------------------------------------------------------------------
// Structure Declaration
//
// ---------------------------------------------------------------------------
template<typename Response, typename Regex, typename Matched>
struct RequestMatcher {
typedef std::function<void(Response&, const RequestMatch<Matched>&)> RequestHandlerType;
typedef RequestMatcher<Response, Regex, Matched> selfType;
/// <summary>Initializes a new instance of the RequestMatch structure.</summary>
explicit RequestMatcher(const Regex& expression) : m_expression(expression) { /* stub */ }
/// <summary></summary>
selfType& get(RequestHandlerType handler) {
m_handlers["GET"] = handler;
return *this;
}
/// <summary></summary>
selfType& post(RequestHandlerType handler) {
m_handlers["POST"] = handler;
return *this;
}
/// <summary></summary>
selfType& put(RequestHandlerType handler) {
m_handlers["PUT"] = handler;
return *this;
}
/// <summary></summary>
selfType& del(RequestHandlerType handler) {
m_handlers["DELETE"] = handler;
return *this;
}
/// <summary></summary>
selfType& options(RequestHandlerType handler) {
m_handlers["OPTIONS"] = handler;
return *this;
}
/// <summary></summary>
template<typename Request>
void handleRequest(const Request& request, Response& response, const Matched &what) {
// dispatching to matching based on handler
RequestMatch<Matched> match(what, request.data);
auto& handler = m_handlers[request.method];
if (handler) {
handler(response, match);
}
}
private:
Regex m_expression;
std::map<std::string, RequestHandlerType> m_handlers;
};
// ---------------------------------------------------------------------------
// Class Declaration
// This class implements RESTful web request dispatching.
// ---------------------------------------------------------------------------
template<typename Request, typename Response, typename Match = std::cmatch, typename Expression = std::regex>
class RequestDispatcher {
typedef RequestMatcher<Response, Expression, Match> MatcherType;
typedef std::shared_ptr<MatcherType> MatcherTypePtr;
public:
/// <summary>Initializes a new instance of the RequestDispatcher class.</summary>
RequestDispatcher() : m_basePath() { /* stub */ }
/// <summary>Initializes a new instance of the RequestDispatcher class.</summary>
RequestDispatcher(const std::string &basePath) : m_basePath(basePath) { /* stub */ }
/// <summary></summary>
MatcherType& match(const Expression& expression)
{
MatcherTypePtr& p = m_matchers[expression];
if(!p) {
p = std::make_shared<crud_matcher_type>(expression);
}
return *p;
}
/// <summary></summary>
template<typename E = Expression>
typename std::enable_if<!std::is_same<E, std::string>::value, void>::type
handleRequest(const Request& request, Response& response)
{
for (const auto& matcher : m_matchers) {
Match what;
if (std::regex_match(request.uri.c_str(), what, matcher.first)) {
matcher.second->handle_request(request, response, what);
}
}
}
/// <summary></summary>
template<typename E = Expression>
typename std::enable_if<std::is_same<E, std::string>::value, void>::type
handleRequest(const Request& request, Response& response)
{
for ( const auto& matcher : m_matchers) {
Match what;
if (request.uri.find(matcher.first) != std::string::npos) {
what = matcher.first;
matcher.second->handle_request(request, response, what);
}
}
}
private:
std::string m_basePath;
std::map<Expression, MatcherTypePtr> m_matchers;
};
} // namespace rest
#endif // __REST__DISPATHER_H__

@ -0,0 +1,174 @@
/**
* Digital Voice Modem - Host Software
* GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* @package DVM / Host Software
*
*/
//
// Based on code from the CRUD project. (https://github.com/venediktov/CRUD)
// Licensed under the BPL-1.0 License (https://opensource.org/license/bsl1-0-html)
//
/*
* Copyright (c) 2003-2013 Christopher M. Kohlhoff
* Copyright (C) 2023 by Bryan Biedenkapp N2PLL
*
* Permission is hereby granted, free of charge, to any person or organization
* obtaining a copy of the software and accompanying documentation covered by
* this license (the Software) to use, reproduce, display, distribute, execute,
* and transmit the Software, and to prepare derivative works of the Software, and
* to permit third-parties to whom the Software is furnished to do so, all subject
* to the following:
*
* The copyright notices in the Software and this entire statement, including the
* above license grant, this restriction and the following disclaimer, must be included
* in all copies of the Software, in whole or in part, and all derivative works of the
* Software, unless such copies or derivative works are solely in the form of
* machine-executable object code generated by a source language processor.
*
* 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, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE
* DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#if !defined(__REST_HTTP__CONNECTION_H__)
#define __REST_HTTP__CONNECTION_H__
#include "Defines.h"
#include "network/rest/http/HTTPReply.h"
#include "network/rest/http/HTTPRequest.h"
#include "network/rest/http/HTTPRequestLexer.h"
#include <array>
#include <memory>
#include <utility>
#include <iterator>
#include <asio.hpp>
namespace rest {
namespace server {
// ---------------------------------------------------------------------------
// Class Prototypes
// ---------------------------------------------------------------------------
template<class> class ConnectionManager;
// ---------------------------------------------------------------------------
// Class Declaration
// This class represents a single connection from a client.
// ---------------------------------------------------------------------------
template <typename RequestHandlerType>
class Connection : public std::enable_shared_from_this<Connection<RequestHandlerType>>
{
typedef Connection<RequestHandlerType> selfType;
typedef std::shared_ptr<selfType> selfTypePtr;
typedef ConnectionManager<selfTypePtr> ConnectionManagerType;
public:
/// <summary>Initializes a new instance of the Connection class.</summary>
explicit Connection(asio::ip::tcp::socket socket, ConnectionManagerType& manager, RequestHandlerType& handler,
bool persistent = false) :
m_socket(std::move(socket)),
m_connectionManager(manager),
m_requestHandler(handler),
m_persistent(persistent)
{
/* stub */
}
/// <summary>Initializes a copy instance of the Connection class.</summary>
Connection(const Connection&) = delete;
/// <summary></summary>
Connection& operator=(const Connection&) = delete;
/// <summary>Start the first asynchronous operation for the connection.</summary>
void start() { read(); }
/// <summary>Stop all asynchronous operations associated with the connection.</summary>
void stop() { m_socket.close(); }
private:
/// <summary>Perform an asynchronous read operation.</summary>
void read()
{
if (!m_persistent) {
auto self(this->shared_from_this());
}
m_socket.async_read_some(asio::buffer(m_buffer), [=](asio::error_code ec, std::size_t bytes_transferred) {
if (!ec) {
HTTPRequestLexer::ResultType result;
char* data;
std::tie(result, data) = m_lexer.parse(m_request, m_buffer.data(), m_buffer.data() + bytes_transferred);
auto itr = std::find_if(m_request.headers.begin(), m_request.headers.end(), [](const header &h) { return h.name == "content-length"; });
if (itr != request_.headers.end()) {
m_request.data = std::string(data, std::static_cast<long>(itr->value));
}
if (result == HTTPRequestLexer::GOOD) {
m_requestHandler.handleRequest(m_request, m_reply);
write();
}
else if (result == HTTPRequestLexer::BAD) {
m_reply = reply::stockReply(HTTPReply::BAD_REQUEST);
write();
}
else {
read();
}
}
else if (ec != asio::error::operation_aborted) {
m_connectionManager.stop(this->shared_from_this());
}
});
}
/// <summary>Perform an asynchronous write operation.</summary>
void write()
{
if (!m_persistent) {
auto self(this->shared_from_this());
} else {
m_reply.headers.emplace_back("Connection:", "keep-alive");
}
asio::async_write(m_socket, m_reply.to_buffers(), [=](asio::error_code ec, std::size_t) {
if (m_persistent) {
m_lexer.reset();
m_reply.headers.resize(0);
m_reply.status = HTTPReply::OK;
m_reply.content = "";
m_request = HTTPRequest();
read();
}
else
{
if (!ec) {
// initiate graceful connection closure
asio::error_code ignored_ec;
m_socket.shutdown(asio::ip::tcp::socket::shutdown_both, ignored_ec);
}
if (ec != asio::error::operation_aborted) {
m_connectionManager.stop(this->shared_from_this());
}
}
});
}
asio::ip::tcp::socket m_socket;
ConnectionManagerType& m_connectionManager;
RequestHandlerType& m_requestHandler;
std::array<char, 8192> m_buffer;
HTTPRequest m_request;
HTTPRequestLexer m_lexer;
HTTPReply m_reply;
bool m_persistent;
};
} // namespace server
} // namespace rest
#endif // __REST_HTTP__CONNECTION_H__

@ -0,0 +1,104 @@
/**
* Digital Voice Modem - Host Software
* GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* @package DVM / Host Software
*
*/
//
// Based on code from the CRUD project. (https://github.com/venediktov/CRUD)
// Licensed under the BPL-1.0 License (https://opensource.org/license/bsl1-0-html)
//
/*
* Copyright (c) 2003-2013 Christopher M. Kohlhoff
* Copyright (C) 2023 by Bryan Biedenkapp N2PLL
*
* Permission is hereby granted, free of charge, to any person or organization
* obtaining a copy of the software and accompanying documentation covered by
* this license (the Software) to use, reproduce, display, distribute, execute,
* and transmit the Software, and to prepare derivative works of the Software, and
* to permit third-parties to whom the Software is furnished to do so, all subject
* to the following:
*
* The copyright notices in the Software and this entire statement, including the
* above license grant, this restriction and the following disclaimer, must be included
* in all copies of the Software, in whole or in part, and all derivative works of the
* Software, unless such copies or derivative works are solely in the form of
* machine-executable object code generated by a source language processor.
*
* 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, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE
* DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#if !defined(__REST_HTTP__CONNECTION_MANAGER_H__)
#define __REST_HTTP__CONNECTION_MANAGER_H__
#include "Defines.h"
#include <set>
#include <mutex>
namespace rest {
namespace server {
// ---------------------------------------------------------------------------
// Class Declaration
// Manages open connections so that they may be cleanly stopped when the server
// needs to shut down.
// ---------------------------------------------------------------------------
template<typename ConnectionPtr>
class ConnectionManager
{
public:
/// <summary>Initializes a new instance of the ConnectionManager class.</summary>
ConnectionManager() { /* stub */ }
/// <summary>Initializes a copy instance of the ConnectionManager class.</summary>
ConnectionManager(const ConnectionManager&) = delete;
/// <summary></summary>
ConnectionManager& operator=(const ConnectionManager&) = delete;
/// <summary>Add the specified connection to the manager and start it.</summary>
void start(ConnectionPtr c)
{
std::lock_guard<std::mutex> guard(m_lock);
{
m_connections.insert(c);
}
c->start();
}
/// <summary>Stop the specified connection.</summary>
void stop(connection_ptr c)
{
std::lock_guard<std::mutex> guard(m_lock);
{
m_connections.erase(c);
}
c->stop();
}
/// <summary>Stop all connections.</summary>
void stopAll()
{
for (auto c : m_connections)
c->stop();
std::lock_guard<std::mutex> guard(m_lock);
m_connections.clear();
}
private:
std::set<ConnectionPtr> m_connections;
std::mutex m_lock;
};
} // namespace server
} // namespace rest
#endif // __REST_HTTP__CONNECTION_MANAGER_H__

@ -0,0 +1,66 @@
/**
* Digital Voice Modem - Host Software
* GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* @package DVM / Host Software
*
*/
//
// Based on code from the CRUD project. (https://github.com/venediktov/CRUD)
// Licensed under the BPL-1.0 License (https://opensource.org/license/bsl1-0-html)
//
/*
* Copyright (c) 2003-2013 Christopher M. Kohlhoff
* Copyright (C) 2023 by Bryan Biedenkapp N2PLL
*
* Permission is hereby granted, free of charge, to any person or organization
* obtaining a copy of the software and accompanying documentation covered by
* this license (the Software) to use, reproduce, display, distribute, execute,
* and transmit the Software, and to prepare derivative works of the Software, and
* to permit third-parties to whom the Software is furnished to do so, all subject
* to the following:
*
* The copyright notices in the Software and this entire statement, including the
* above license grant, this restriction and the following disclaimer, must be included
* in all copies of the Software, in whole or in part, and all derivative works of the
* Software, unless such copies or derivative works are solely in the form of
* machine-executable object code generated by a source language processor.
*
* 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, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE
* DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#if !defined(__REST_HTTP__HTTP_HEADER_H__)
#define __REST_HTTP__HTTP_HEADER_H__
#include "Defines.h"
#include <string>
namespace rest {
namespace server {
// ---------------------------------------------------------------------------
// Structure Declaration
// This class implements a model for an HTTP header.
// ---------------------------------------------------------------------------
struct HTTPHeader
{
std::string name;
std::string value;
/// <summary>Initializes a new instance of the HTTPHeader struct.</summary>
HTTPHeader() { /* stub */ }
/// <summary>Initializes a new instance of the HTTPHeader struct.</summary>
HTTPHeader(const std::string& name, const std::string& value) : name{name}, value{value} { /* stub */ }
};
} // namespace server
} // namespace rest
#endif // __REST_HTTP__HTTP_HEADER_H__

@ -0,0 +1,267 @@
/**
* Digital Voice Modem - Host Software
* GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* @package DVM / Host Software
*
*/
//
// Based on code from the CRUD project. (https://github.com/venediktov/CRUD)
// Licensed under the BPL-1.0 License (https://opensource.org/license/bsl1-0-html)
//
/*
* Copyright (c) 2003-2013 Christopher M. Kohlhoff
* Copyright (C) 2023 by Bryan Biedenkapp N2PLL
*
* Permission is hereby granted, free of charge, to any person or organization
* obtaining a copy of the software and accompanying documentation covered by
* this license (the Software) to use, reproduce, display, distribute, execute,
* and transmit the Software, and to prepare derivative works of the Software, and
* to permit third-parties to whom the Software is furnished to do so, all subject
* to the following:
*
* The copyright notices in the Software and this entire statement, including the
* above license grant, this restriction and the following disclaimer, must be included
* in all copies of the Software, in whole or in part, and all derivative works of the
* Software, unless such copies or derivative works are solely in the form of
* machine-executable object code generated by a source language processor.
*
* 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, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE
* DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "Defines.h"
#include "network/rest/http/HTTPReply.h"
using namespace rest::server;
#include <string>
namespace status_strings {
const std::string ok = "HTTP/1.0 200 OK\r\n";
const std::string created = "HTTP/1.0 201 Created\r\n";
const std::string accepted = "HTTP/1.0 202 Accepted\r\n";
const std::string no_content = "HTTP/1.0 204 No Content\r\n";
const std::string multiple_choices = "HTTP/1.0 300 Multiple Choices\r\n";
const std::string moved_permanently = "HTTP/1.0 301 Moved Permanently\r\n";
const std::string moved_temporarily = "HTTP/1.0 302 Moved Temporarily\r\n";
const std::string not_modified = "HTTP/1.0 304 Not Modified\r\n";
const std::string bad_request = "HTTP/1.0 400 Bad Request\r\n";
const std::string unauthorized = "HTTP/1.0 401 Unauthorized\r\n";
const std::string forbidden = "HTTP/1.0 403 Forbidden\r\n";
const std::string not_found = "HTTP/1.0 404 Not Found\r\n";
const std::string internal_server_error = "HTTP/1.0 500 Internal Server Error\r\n";
const std::string not_implemented = "HTTP/1.0 501 Not Implemented\r\n";
const std::string bad_gateway = "HTTP/1.0 502 Bad Gateway\r\n";
const std::string service_unavailable = "HTTP/1.0 503 Service Unavailable\r\n";
asio::const_buffer toBuffer(HTTPReply::StatusType status)
{
switch (status)
{
case HTTPReply::OK:
return asio::buffer(ok);
case HTTPReply::CREATED:
return asio::buffer(created);
case HTTPReply::ACCEPTED:
return asio::buffer(accepted);
case HTTPReply::NO_CONTENT:
return asio::buffer(no_content);
case HTTPReply::MULTIPLE_CHOICES:
return asio::buffer(multiple_choices);
case HTTPReply::MOVED_PERMANENTLY:
return asio::buffer(moved_permanently);
case HTTPReply::MOVED_TEMPORARILY:
return asio::buffer(moved_temporarily);
case HTTPReply::NOT_MODIFIED:
return asio::buffer(not_modified);
case HTTPReply::BAD_REQUEST:
return asio::buffer(bad_request);
case HTTPReply::UNAUTHORIZED:
return asio::buffer(unauthorized);
case HTTPReply::FORBIDDEN:
return asio::buffer(forbidden);
case HTTPReply::NOT_FOUND:
return asio::buffer(not_found);
case HTTPReply::INTERNAL_SERVER_ERROR:
return asio::buffer(internal_server_error);
case HTTPReply::NOT_IMPLEMENTED:
return asio::buffer(not_implemented);
case HTTPReply::BAD_GATEWAY:
return asio::buffer(bad_gateway);
case HTTPReply::SERVICE_UNAVAILABLE:
return asio::buffer(service_unavailable);
default:
return asio::buffer(internal_server_error);
}
}
} // namespace status_strings
namespace misc_strings {
const char name_value_separator[] = { ':', ' ' };
const char crlf[] = { '\r', '\n' };
} // namespace misc_strings
std::vector<asio::const_buffer> HTTPReply::toBuffers()
{
std::vector<asio::const_buffer> buffers;
buffers.push_back(status_strings::toBuffer(status));
for (std::size_t i = 0; i < headers.size(); ++i) {
HTTPHeader& h = headers[i];
buffers.push_back(asio::buffer(h.name));
buffers.push_back(asio::buffer(misc_strings::name_value_separator));
buffers.push_back(asio::buffer(h.value));
buffers.push_back(asio::buffer(misc_strings::crlf));
}
buffers.push_back(asio::buffer(misc_strings::crlf));
buffers.push_back(asio::buffer(content));
return buffers;
}
namespace stock_replies {
const char ok[] = "";
const char created[] =
"<html>"
"<head><title>Created</title></head>"
"<body><h1>201 Created</h1></body>"
"</html>";
const char accepted[] =
"<html>"
"<head><title>Accepted</title></head>"
"<body><h1>202 Accepted</h1></body>"
"</html>";
const char no_content[] =
"<html>"
"<head><title>No Content</title></head>"
"<body><h1>204 Content</h1></body>"
"</html>";
const char multiple_choices[] =
"<html>"
"<head><title>Multiple Choices</title></head>"
"<body><h1>300 Multiple Choices</h1></body>"
"</html>";
const char moved_permanently[] =
"<html>"
"<head><title>Moved Permanently</title></head>"
"<body><h1>301 Moved Permanently</h1></body>"
"</html>";
const char moved_temporarily[] =
"<html>"
"<head><title>Moved Temporarily</title></head>"
"<body><h1>302 Moved Temporarily</h1></body>"
"</html>";
const char not_modified[] =
"<html>"
"<head><title>Not Modified</title></head>"
"<body><h1>304 Not Modified</h1></body>"
"</html>";
const char bad_request[] =
"<html>"
"<head><title>Bad Request</title></head>"
"<body><h1>400 Bad Request</h1></body>"
"</html>";
const char unauthorized[] =
"<html>"
"<head><title>Unauthorized</title></head>"
"<body><h1>401 Unauthorized</h1></body>"
"</html>";
const char forbidden[] =
"<html>"
"<head><title>Forbidden</title></head>"
"<body><h1>403 Forbidden</h1></body>"
"</html>";
const char not_found[] =
"<html>"
"<head><title>Not Found</title></head>"
"<body><h1>404 Not Found</h1></body>"
"</html>";
const char internal_server_error[] =
"<html>"
"<head><title>Internal Server Error</title></head>"
"<body><h1>500 Internal Server Error</h1></body>"
"</html>";
const char not_implemented[] =
"<html>"
"<head><title>Not Implemented</title></head>"
"<body><h1>501 Not Implemented</h1></body>"
"</html>";
const char bad_gateway[] =
"<html>"
"<head><title>Bad Gateway</title></head>"
"<body><h1>502 Bad Gateway</h1></body>"
"</html>";
const char service_unavailable[] =
"<html>"
"<head><title>Service Unavailable</title></head>"
"<body><h1>503 Service Unavailable</h1></body>"
"</html>";
std::string to_string(HTTPReply::StatusType status)
{
switch (status)
{
case HTTPReply::OK:
return ok;
case HTTPReply::CREATED:
return created;
case HTTPReply::ACCEPTED:
return accepted;
case HTTPReply::NO_CONTENT:
return no_content;
case HTTPReply::MULTIPLE_CHOICES:
return multiple_choices;
case HTTPReply::MOVED_PERMANENTLY:
return moved_permanently;
case HTTPReply::MOVED_TEMPORARILY:
return moved_temporarily;
case HTTPReply::NOT_MODIFIED:
return not_modified;
case HTTPReply::BAD_REQUEST:
return bad_request;
case HTTPReply::UNAUTHORIZED:
return unauthorized;
case HTTPReply::FORBIDDEN:
return forbidden;
case HTTPReply::NOT_FOUND:
return not_found;
case HTTPReply::INTERNAL_SERVER_ERROR:
return internal_server_error;
case HTTPReply::NOT_IMPLEMENTED:
return not_implemented;
case HTTPReply::BAD_GATEWAY:
return bad_gateway;
case HTTPReply::SERVICE_UNAVAILABLE:
return service_unavailable;
default:
return internal_server_error;
}
}
} // namespace stock_replies
HTTPReply HTTPReply::stockReply(HTTPReply::StatusType status, const char* mime)
{
HTTPReply rep;
rep.status = status;
if (status != HTTPReply::NO_CONTENT) {
rep.content = stock_replies::to_string(status);
rep.headers.resize(2);
rep.headers[0].name = "Content-Length";
rep.headers[0].value = std::to_string(rep.content.size());
rep.headers[1].name = "Content-Type";
rep.headers[1].value = mime;
}
return rep;
}
HTTPReply& operator<<(HTTPReply& r, const std::string &value) {
r.content.append(value);
return r;
}

@ -0,0 +1,93 @@
/**
* Digital Voice Modem - Host Software
* GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* @package DVM / Host Software
*
*/
//
// Based on code from the CRUD project. (https://github.com/venediktov/CRUD)
// Licensed under the BPL-1.0 License (https://opensource.org/license/bsl1-0-html)
//
/*
* Copyright (c) 2003-2013 Christopher M. Kohlhoff
* Copyright (C) 2023 by Bryan Biedenkapp N2PLL
*
* Permission is hereby granted, free of charge, to any person or organization
* obtaining a copy of the software and accompanying documentation covered by
* this license (the Software) to use, reproduce, display, distribute, execute,
* and transmit the Software, and to prepare derivative works of the Software, and
* to permit third-parties to whom the Software is furnished to do so, all subject
* to the following:
*
* The copyright notices in the Software and this entire statement, including the
* above license grant, this restriction and the following disclaimer, must be included
* in all copies of the Software, in whole or in part, and all derivative works of the
* Software, unless such copies or derivative works are solely in the form of
* machine-executable object code generated by a source language processor.
*
* 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, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE
* DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#if !defined(__REST_HTTP__HTTP_REPLY_H__)
#define __REST_HTTP__HTTP_REPLY_H__
#include "Defines.h"
#include "network/rest/http/HTTPHeader.h"
#include <string>
#include <vector>
#include <asio.hpp>
namespace rest {
namespace server {
// ---------------------------------------------------------------------------
// Structure Declaration
// This struct implements a model of a reply to be sent to a HTTP client.
// ---------------------------------------------------------------------------
struct HTTPReply
{
enum StatusType {
OK = 200,
CREATED = 201,
ACCEPTED = 202,
NO_CONTENT = 204,
MULTIPLE_CHOICES = 300,
MOVED_PERMANENTLY = 301,
MOVED_TEMPORARILY = 302,
NOT_MODIFIED = 304,
BAD_REQUEST = 400,
UNAUTHORIZED = 401,
FORBIDDEN = 403,
NOT_FOUND = 404,
INTERNAL_SERVER_ERROR = 500,
NOT_IMPLEMENTED = 501,
BAD_GATEWAY = 502,
SERVICE_UNAVAILABLE = 503
} status;
std::vector<HTTPHeader> headers;
std::string content;
/// <summary>Convert the reply into a vector of buffers. The buffers do not own the
/// underlying memory blocks, therefore the reply object must remain valid and
/// not be changed until the write operation has completed.</summary>
std::vector<asio::const_buffer> toBuffers();
/// <summary>Get a stock reply.</summary>
static HTTPReply stockReply(StatusType status, const char* mime = "text/html");
};
HTTPReply& operator<<(HTTPReply& r, const std::string& value);
} // namespace server
} // namespace rest
#endif // __REST_HTTP__HTTP_REPLY_H__

@ -0,0 +1,68 @@
/**
* Digital Voice Modem - Host Software
* GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* @package DVM / Host Software
*
*/
//
// Based on code from the CRUD project. (https://github.com/venediktov/CRUD)
// Licensed under the BPL-1.0 License (https://opensource.org/license/bsl1-0-html)
//
/*
* Copyright (c) 2003-2013 Christopher M. Kohlhoff
* Copyright (C) 2023 by Bryan Biedenkapp N2PLL
*
* Permission is hereby granted, free of charge, to any person or organization
* obtaining a copy of the software and accompanying documentation covered by
* this license (the Software) to use, reproduce, display, distribute, execute,
* and transmit the Software, and to prepare derivative works of the Software, and
* to permit third-parties to whom the Software is furnished to do so, all subject
* to the following:
*
* The copyright notices in the Software and this entire statement, including the
* above license grant, this restriction and the following disclaimer, must be included
* in all copies of the Software, in whole or in part, and all derivative works of the
* Software, unless such copies or derivative works are solely in the form of
* machine-executable object code generated by a source language processor.
*
* 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, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE
* DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#if !defined(__REST_HTTP__HTTP_REQUEST_H__)
#define __REST_HTTP__HTTP_REQUEST_H__
#include "Defines.h"
#include "network/rest/http/HTTPHeader.h"
#include <string>
#include <vector>
namespace rest {
namespace server {
// ---------------------------------------------------------------------------
// Structure Declaration
// This struct implements a model of a request received from a HTTP client.
// ---------------------------------------------------------------------------
struct HTTPRequest
{
std::string method;
std::string uri;
int httpVersionMajor;
int httpVersionMinor;
std::vector<HTTPHeader> headers;
std::string data;
};
} // namespace server
} // namespace http
#endif // __REST_HTTP__HTTP_REQUEST_H__

@ -0,0 +1,152 @@
/**
* Digital Voice Modem - Host Software
* GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* @package DVM / Host Software
*
*/
//
// Based on code from the CRUD project. (https://github.com/venediktov/CRUD)
// Licensed under the BPL-1.0 License (https://opensource.org/license/bsl1-0-html)
//
/*
* Copyright (c) 2003-2013 Christopher M. Kohlhoff
* Copyright (C) 2023 by Bryan Biedenkapp N2PLL
*
* Permission is hereby granted, free of charge, to any person or organization
* obtaining a copy of the software and accompanying documentation covered by
* this license (the Software) to use, reproduce, display, distribute, execute,
* and transmit the Software, and to prepare derivative works of the Software, and
* to permit third-parties to whom the Software is furnished to do so, all subject
* to the following:
*
* The copyright notices in the Software and this entire statement, including the
* above license grant, this restriction and the following disclaimer, must be included
* in all copies of the Software, in whole or in part, and all derivative works of the
* Software, unless such copies or derivative works are solely in the form of
* machine-executable object code generated by a source language processor.
*
* 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, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE
* DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "Defines.h"
#include "network/rest/http/HTTPRequestHandler.h"
#include "network/rest/http/HTTPRequest.h"
#include "network/rest/http/HTTPReply.h"
using namespace rest::server;
#include <fstream>
#include <sstream>
#include <string>
// ---------------------------------------------------------------------------
// Public Class Members
// ---------------------------------------------------------------------------
/// <summary>
/// Initializes a new instance of the HTTPRequestHandler class.
/// </summary>
/// <param name="docRoot"></param>
HTTPRequestHandler::HTTPRequestHandler(const std::string& docRoot) :
m_docRoot(docRoot)
{
/* stub */
}
/// <summary>
/// Handle a request and produce a reply.
/// </summary>
void HTTPRequestHandler::handleRequest(const HTTPRequest& request, HTTPReply& reply)
{
// decode url to path
std::string requestPath;
if (!urlDecode(request.uri, requestPath)) {
reply = HTTPReply::stockReply(HTTPReply::BAD_REQUEST);
return;
}
// request path must be absolute and not contain "..".
if (requestPath.empty() || requestPath[0] != '/' ||
requestPath.find("..") != std::string::npos) {
reply = HTTPReply::stockReply(HTTPReply::BAD_REQUEST);
return;
}
// if path ends in slash (i.e. is a directory) then add "index.html"
if (requestPath[requestPath.size() - 1] == '/') {
requestPath += "index.html";
}
// determine the file extension
std::size_t lastSlashPos = requestPath.find_last_of("/");
std::size_t lastDotPos = requestPath.find_last_of(".");
std::string extension;
if (lastDotPos != std::string::npos && lastDotPos > lastSlashPos) {
extension = requestPath.substr(lastDotPos + 1);
}
// open the file to send back
std::string fullPath = m_docRoot + requestPath;
std::ifstream is(fullPath.c_str(), std::ios::in | std::ios::binary);
if (!is) {
reply = HTTPReply::stockReply(HTTPReply::NOT_FOUND);
return;
}
// fill out the reply to be sent to the client
reply.status = HTTPReply::OK;
char buf[512];
while (is.read(buf, sizeof(buf)).gcount() > 0)
reply.content.append(buf, is.gcount());
reply.headers.resize(2);
reply.headers[0].name = "Content-Length";
reply.headers[0].value = std::to_string(reply.content.size());
reply.headers[1].name = "Content-Type";
reply.headers[1].value = "application/octet-stream";
}
/// <summary>
/// Perform URL-decoding on a string. Returns false if the encoding was invalid.
/// </summary>
/// <param name="in"></param>
/// <param name="out"></param>
bool HTTPRequestHandler::urlDecode(const std::string& in, std::string& out)
{
out.clear();
out.reserve(in.size());
for (std::size_t i = 0; i < in.size(); ++i) {
if (in[i] == '%') {
if (i + 3 <= in.size()) {
int value = 0;
std::istringstream is(in.substr(i + 1, 2));
if (is >> std::hex >> value) {
out += static_cast<char>(value);
i += 2;
}
else {
return false;
}
}
else {
return false;
}
}
else if (in[i] == '+') {
out += ' ';
}
else {
out += in[i];
}
}
return true;
}

@ -0,0 +1,87 @@
/**
* Digital Voice Modem - Host Software
* GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* @package DVM / Host Software
*
*/
//
// Based on code from the CRUD project. (https://github.com/venediktov/CRUD)
// Licensed under the BPL-1.0 License (https://opensource.org/license/bsl1-0-html)
//
/*
* Copyright (c) 2003-2013 Christopher M. Kohlhoff
* Copyright (C) 2023 by Bryan Biedenkapp N2PLL
*
* Permission is hereby granted, free of charge, to any person or organization
* obtaining a copy of the software and accompanying documentation covered by
* this license (the Software) to use, reproduce, display, distribute, execute,
* and transmit the Software, and to prepare derivative works of the Software, and
* to permit third-parties to whom the Software is furnished to do so, all subject
* to the following:
*
* The copyright notices in the Software and this entire statement, including the
* above license grant, this restriction and the following disclaimer, must be included
* in all copies of the Software, in whole or in part, and all derivative works of the
* Software, unless such copies or derivative works are solely in the form of
* machine-executable object code generated by a source language processor.
*
* 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, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE
* DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#if !defined(__REST_HTTP__HTTP_REQUEST_HANDLER_H__)
#define __REST_HTTP__HTTP_REQUEST_HANDLER_H__
#include "Defines.h"
#include <string>
namespace rest {
namespace server {
// ---------------------------------------------------------------------------
// Class Prototypes
// ---------------------------------------------------------------------------
struct HTTPReply;
struct HTTPRequest;
// ---------------------------------------------------------------------------
// Class Declaration
// This class implements the common handler for all incoming requests.
// ---------------------------------------------------------------------------
class HTTPRequestHandler
{
public:
/// <summary>Initializes a new instance of the HTTPRequestHandler class.</summary>
explicit HTTPRequestHandler(const std::string& docRoot);
/// <summary>Initializes a copy instance of the HTTPRequestHandler class.</summary>
HTTPRequestHandler(const HTTPRequestHandler&) = delete;
/// <summary></summary>
HTTPRequestHandler(HTTPRequestHandler&&) = default;
/// <summary></summary>
HTTPRequestHandler& operator=(const HTTPRequestHandler&) = delete;
/// <summary></summary>
HTTPRequestHandler& operator=(HTTPRequestHandler&&) = default;
/// <summary>Handle a request and produce a reply.</summary>
void handleRequest(const HTTPRequest& req, HTTPReply& reply);
private:
/// <summary>Perform URL-decoding on a string. Returns false if the encoding was
/// invalid.</summary>
static bool urlDecode(const std::string& in, std::string& out);
std::string m_docRoot;
};
} // namespace server
} // namespace rest
#endif // __REST_HTTP__HTTP_REQUEST_HANDLER_H__

@ -0,0 +1,361 @@
/**
* Digital Voice Modem - Host Software
* GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* @package DVM / Host Software
*
*/
//
// Based on code from the CRUD project. (https://github.com/venediktov/CRUD)
// Licensed under the BPL-1.0 License (https://opensource.org/license/bsl1-0-html)
//
/*
* Copyright (c) 2003-2013 Christopher M. Kohlhoff
* Copyright (C) 2023 by Bryan Biedenkapp N2PLL
*
* Permission is hereby granted, free of charge, to any person or organization
* obtaining a copy of the software and accompanying documentation covered by
* this license (the Software) to use, reproduce, display, distribute, execute,
* and transmit the Software, and to prepare derivative works of the Software, and
* to permit third-parties to whom the Software is furnished to do so, all subject
* to the following:
*
* The copyright notices in the Software and this entire statement, including the
* above license grant, this restriction and the following disclaimer, must be included
* in all copies of the Software, in whole or in part, and all derivative works of the
* Software, unless such copies or derivative works are solely in the form of
* machine-executable object code generated by a source language processor.
*
* 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, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE
* DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "Defines.h"
#include "network/rest/http/HTTPRequestLexer.h"
#include "network/rest/http/HTTPRequest.h"
using namespace rest::server;
#include <cctype>
// ---------------------------------------------------------------------------
// Public Class Members
// ---------------------------------------------------------------------------
/// <summary>
/// Initializes a new instance of the HTTPRequestLexer class.
/// </summary>
HTTPRequestLexer::HTTPRequestLexer() :
m_state(METHOD_START)
{
/* stub */
}
/// <summary>Reset to initial parser state.</summary>
void HTTPRequestLexer::reset()
{
m_state = METHOD_START;
}
// ---------------------------------------------------------------------------
// Private Class Members
// ---------------------------------------------------------------------------
/// <summary>
/// Handle the next character of input.
/// </summary>
/// <param name="req"></param>
/// <param name="input"></param>
/// <returns></returns>
HTTPRequestLexer::ResultType HTTPRequestLexer::consume(HTTPRequest& req, char input)
{
switch (m_state)
{
/*
** HTTP Method
*/
case METHOD_START:
if (!isChar(input) || isControl(input) || isSpecial(input))
return BAD;
else {
m_state = METHOD;
req.method.push_back(input);
return INDETERMINATE;
}
case METHOD:
if (input == ' ') {
m_state = URI;
return INDETERMINATE;
}
else if (!isChar(input) || isControl(input) || isSpecial(input)) {
return BAD;
}
else {
req.method.push_back(input);
return INDETERMINATE;
}
/*
** URI
*/
case URI:
if (input == ' ') {
m_state = HTTP_VERSION_H;
return INDETERMINATE;
}
else if (isControl(input)) {
return BAD;
}
else {
req.uri.push_back(input);
return INDETERMINATE;
}
/*
** HTTP/1.0
*/
case HTTP_VERSION_H:
if (input == 'H') {
m_state = HTTP_VERSION_T_1;
return INDETERMINATE;
}
else {
return BAD;
}
case HTTP_VERSION_T_1:
if (input == 'T') {
m_state = HTTP_VERSION_T_2;
return INDETERMINATE;
}
else {
return BAD;
}
case HTTP_VERSION_T_2:
if (input == 'T') {
m_state = HTTP_VERSION_P;
return INDETERMINATE;
}
else {
return BAD;
}
case HTTP_VERSION_P:
if (input == 'P') {
m_state = HTTP_VERSION_SLASH;
return INDETERMINATE;
}
else {
return BAD;
}
case HTTP_VERSION_SLASH:
if (input == '/') {
req.httpVersionMajor = 0;
req.httpVersionMinor = 0;
m_state = HTTP_VERSION_MAJOR_START;
return INDETERMINATE;
}
else {
return BAD;
}
case HTTP_VERSION_MAJOR_START:
if (isDigit(input)) {
req.httpVersionMajor = req.httpVersionMajor * 10 + input - '0';
m_state = HTTP_VERSION_MAJOR;
return INDETERMINATE;
}
else {
return BAD;
}
case HTTP_VERSION_MAJOR:
if (input == '.') {
m_state = HTTP_VERSION_MINOR_START;
return INDETERMINATE;
}
else if (isDigit(input)) {
req.httpVersionMajor = req.httpVersionMajor * 10 + input - '0';
return INDETERMINATE;
}
else {
return BAD;
}
case HTTP_VERSION_MINOR_START:
if (isDigit(input)) {
req.httpVersionMinor = req.httpVersionMinor * 10 + input - '0';
m_state = HTTP_VERSION_MINOR;
return INDETERMINATE;
}
else {
return BAD;
}
case HTTP_VERSION_MINOR:
if (input == '\r')
{
m_state = EXPECTING_NEWLINE_1;
return INDETERMINATE;
}
else if (isDigit(input))
{
req.httpVersionMinor = req.httpVersionMinor * 10 + input - '0';
return INDETERMINATE;
}
else {
return BAD;
}
case EXPECTING_NEWLINE_1:
if (input == '\n') {
m_state = HEADER_LINE_START;
return INDETERMINATE;
}
else {
return BAD;
}
/*
** Headers
*/
case HEADER_LINE_START:
if (input == '\r') {
m_state = EXPECTING_NEWLINE_3;
return INDETERMINATE;
}
else if (!req.headers.empty() && (input == ' ' || input == '\t')) {
m_state = HEADER_LWS;
return INDETERMINATE;
}
else if (!isChar(input) || isControl(input) || isSpecial(input)) {
return BAD;
}
else {
req.headers.push_back(HTTPHeader());
req.headers.back().name.push_back(std::tolower(input));
m_state = HEADER_NAME;
return INDETERMINATE;
}
case HEADER_LWS:
if (input == '\r') {
m_state = EXPECTING_NEWLINE_2;
return INDETERMINATE;
}
else if (input == ' ' || input == '\t') {
return INDETERMINATE;
}
else if (isControl(input)) {
return BAD;
}
else {
m_state = HEADER_VALUE;
req.headers.back().value.push_back(input);
return INDETERMINATE;
}
case HEADER_NAME:
if (input == ':') {
m_state = SPACE_BEFORE_HEADER_VALUE;
return INDETERMINATE;
}
else if (!isChar(input) || isControl(input) || isSpecial(input)) {
return BAD;
}
else
{
req.headers.back().name.push_back(std::tolower(input));
return INDETERMINATE;
}
case SPACE_BEFORE_HEADER_VALUE:
if (input == ' ')
{
m_state = HEADER_VALUE;
return INDETERMINATE;
}
else {
return BAD;
}
case HEADER_VALUE:
if (input == '\r') {
m_state = EXPECTING_NEWLINE_2;
return INDETERMINATE;
}
else if (isControl(input)) {
return BAD;
}
else {
req.headers.back().value.push_back(input);
return INDETERMINATE;
}
case EXPECTING_NEWLINE_2:
if (input == '\n') {
m_state = HEADER_LINE_START;
return INDETERMINATE;
}
else {
return BAD;
}
case EXPECTING_NEWLINE_3:
return (input == '\n') ? GOOD : BAD;
default:
return BAD;
}
}
/// <summary>
/// Check if a byte is an HTTP character.
/// </summary>
/// <param name="c"></param>
/// <returns></returns>
bool HTTPRequestLexer::isChar(int c)
{
return c >= 0 && c <= 127;
}
/// <summary>
/// Check if a byte is an HTTP control character.
/// </summary>
/// <param name="c"></param>
/// <returns></returns>
bool HTTPRequestLexer::isControl(int c)
{
return (c >= 0 && c <= 31) || (c == 127);
}
/// <summary>
/// Check if a byte is an HTTP special character.
/// </summary>
/// <param name="c"></param>
/// <returns></returns>
bool HTTPRequestLexer::isSpecial(int c)
{
switch (c)
{
case '(': case ')': case '<': case '>': case '@':
case ',': case ';': case ':': case '\\': case '"':
case '/': case '[': case ']': case '?': case '=':
case '{': case '}': case ' ': case '\t':
return true;
default:
return false;
}
}
/// <summary>
/// Check if a byte is an digit.
/// </summary>
/// <param name="c"></param>
/// <returns></returns>
bool HTTPRequestLexer::isDigit(int c)
{
return c >= '0' && c <= '9';
}

@ -0,0 +1,122 @@
/**
* Digital Voice Modem - Host Software
* GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* @package DVM / Host Software
*
*/
//
// Based on code from the CRUD project. (https://github.com/venediktov/CRUD)
// Licensed under the BPL-1.0 License (https://opensource.org/license/bsl1-0-html)
//
/*
* Copyright (c) 2003-2013 Christopher M. Kohlhoff
* Copyright (C) 2023 by Bryan Biedenkapp N2PLL
*
* Permission is hereby granted, free of charge, to any person or organization
* obtaining a copy of the software and accompanying documentation covered by
* this license (the Software) to use, reproduce, display, distribute, execute,
* and transmit the Software, and to prepare derivative works of the Software, and
* to permit third-parties to whom the Software is furnished to do so, all subject
* to the following:
*
* The copyright notices in the Software and this entire statement, including the
* above license grant, this restriction and the following disclaimer, must be included
* in all copies of the Software, in whole or in part, and all derivative works of the
* Software, unless such copies or derivative works are solely in the form of
* machine-executable object code generated by a source language processor.
*
* 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, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE
* DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#if !defined(__REST_HTTP__HTTP_REQUEST_PARSER_H__)
#define __REST_HTTP__HTTP_REQUEST_PARSER_H__
#include <tuple>
namespace rest {
namespace server {
// ---------------------------------------------------------------------------
// Class Prototypes
// ---------------------------------------------------------------------------
struct HTTPRequest;
// ---------------------------------------------------------------------------
// Class Declaration
// This class implements the lexer for incoming requests.
// ---------------------------------------------------------------------------
class HTTPRequestLexer
{
public:
enum ResultType { GOOD, BAD, INDETERMINATE };
/// <summary>Initializes a new instance of the HTTPRequestLexer class.</summary>
HTTPRequestLexer();
/// <summary>Reset to initial parser state.</summary>
void reset();
/// <summary>Parse some data. The enum return value is good when a complete request has
/// been parsed, bad if the data is invalid, indeterminate when more data is
/// required. The InputIterator return value indicates how much of the input
/// has been consumed.</summary>
template <typename InputIterator>
std::tuple<ResultType, InputIterator> parse(HTTPRequest& req, InputIterator begin, InputIterator end)
{
while (begin != end) {
ResultType result = consume(req, *begin++);
if (result == GOOD || result == BAD)
return std::make_tuple(result, begin);
}
return std::make_tuple(INDETERMINATE, begin);
}
private:
/// <summary>Handle the next character of input.</summary>
ResultType consume(HTTPRequest& req, char input);
/// <summary>Check if a byte is an HTTP character.</summary>
static bool isChar(int c);
/// <summary>Check if a byte is an HTTP control character.</summary>
static bool isControl(int c);
/// <summary>Check if a byte is an HTTP special character.</summary>
static bool isSpecial(int c);
/// <summary>Check if a byte is an digit.</summary>
static bool isDigit(int c);
enum state
{
METHOD_START,
METHOD,
URI,
HTTP_VERSION_H,
HTTP_VERSION_T_1,
HTTP_VERSION_T_2,
HTTP_VERSION_P,
HTTP_VERSION_SLASH,
HTTP_VERSION_MAJOR_START,
HTTP_VERSION_MAJOR,
HTTP_VERSION_MINOR_START,
HTTP_VERSION_MINOR,
EXPECTING_NEWLINE_1,
HEADER_LINE_START,
HEADER_LWS,
HEADER_NAME,
SPACE_BEFORE_HEADER_VALUE,
HEADER_VALUE,
EXPECTING_NEWLINE_2,
EXPECTING_NEWLINE_3
} m_state;
};
} // namespace server
} // namespace rest
#endif // __REST_HTTP__HTTP_REQUEST_PARSER_H__

@ -0,0 +1,160 @@
/**
* Digital Voice Modem - Host Software
* GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* @package DVM / Host Software
*
*/
//
// Based on code from the CRUD project. (https://github.com/venediktov/CRUD)
// Licensed under the BPL-1.0 License (https://opensource.org/license/bsl1-0-html)
//
/*
* Copyright (c) 2003-2013 Christopher M. Kohlhoff
* Copyright (C) 2023 by Bryan Biedenkapp N2PLL
*
* Permission is hereby granted, free of charge, to any person or organization
* obtaining a copy of the software and accompanying documentation covered by
* this license (the Software) to use, reproduce, display, distribute, execute,
* and transmit the Software, and to prepare derivative works of the Software, and
* to permit third-parties to whom the Software is furnished to do so, all subject
* to the following:
*
* The copyright notices in the Software and this entire statement, including the
* above license grant, this restriction and the following disclaimer, must be included
* in all copies of the Software, in whole or in part, and all derivative works of the
* Software, unless such copies or derivative works are solely in the form of
* machine-executable object code generated by a source language processor.
*
* 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, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE
* DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#if !defined(__REST_HTTP__HTTP_SERVER_H__)
#define __REST_HTTP__HTTP_SERVER_H__
#include "Defines.h"
#include "network/rest/Connection.h"
#include "network/rest/ConnectionManager.h"
#include "network/rest/HTTPRequestHandler.h"
#include <asio.hpp>
#include <thread>
#include <string>
#include <signal.h>
#include <utility>
#include <memory>
namespace rest {
namespace server {
// ---------------------------------------------------------------------------
// Class Declaration
// This class implements top-level routines of the HTTP server.
// ---------------------------------------------------------------------------
template<typename RequestHandlerType, template<class> class ConnectionImpl = Connection>
class HTTPServer {
public:
/// <summary>Initializes a new instance of the HTTPServer class.</summary>
template<typename Handler>
explicit HTTPServer(const std::string& address, const std::string& port, Handler&& handler) :
m_ioService(),
m_signals(m_ioService),
m_acceptor(m_ioService),
m_connectionManager(),
m_socket(m_ioService),
m_requestHandler(std::forward<Handler>(handler))
{
// register to handle the signals that indicate when the server should exit
// it is safe to register for the same signal multiple times in a program,
// provided all registration for the specified signal is made through ASIO
m_signals.add(SIGINT);
m_signals.add(SIGTERM);
#if defined(SIGQUIT)
m_signals.add(SIGQUIT);
#endif
awaitStop();
// open the acceptor with the option to reuse the address (i.e. SO_REUSEADDR)
asio::ip::tcp::resolver resolver(m_ioService);
asio::ip::tcp::endpoint endpoint = *resolver.resolve({address, port});
m_acceptor.open(endpoint.protocol());
m_acceptor.set_option(asio::ip::tcp::acceptor::reuse_address(true));
m_acceptor.set_option(asio::socket_base::keep_alive(true));
m_acceptor.bind(endpoint);
m_acceptor.listen();
accept();
}
/// <summary>Initializes a copy instance of the HTTPServer class.</summary>
HTTPServer(const HTTPServer&) = delete;
/// <summary></summary>
HTTPServer& operator=(const HTTPServer&) = delete;
/// <summary>Run the servers ASIO IO service loop.</summary>
void run()
{
// the run() call will block until all asynchronous operations
// have finished; while the server is running, there is always at least one
// asynchronous operation outstanding: the asynchronous accept call waiting
// for new incoming connections
m_ioService.run();
}
private:
/// <summary>Perform an asynchronous accept operation.</summary>
void accept()
{
m_acceptor.async_accept(m_socket, [this](asio::error_code ec) {
// check whether the server was stopped by a signal before this
// completion handler had a chance to run
if (!m_acceptor.is_open()) {
return;
}
if (!ec) {
m_connectionManager.start(std::make_shared<connection_type>(std::move(m_socket), m_connectionManager, m_requestHandler));
}
accept();
});
}
/// <summary>Wait for a request to stop the server.</summary>
void awaitStop()
{
m_signals.async_wait([this](asio::error_code /*ec*/, int /*signo*/) {
// the server is stopped by cancelling all outstanding asynchronous
// operations; once all operations have finished the m_ioService::run()
// call will exit
m_acceptor.close();
m_connectionManager.stopAll();
});
}
typedef ConnectionImpl<RequestHandlerType> ConnectionType;
typedef std::shared_ptr<ConnectionType> ConnectionTypePtr;
asio::io_service m_ioService;
asio::signal_set m_signals;
asio::ip::tcp::acceptor m_acceptor;
ConnectionManager<ConnectionTypePtr> m_connectionManager;
asio::ip::tcp::socket m_socket;
RequestHandlerType m_requestHandler;
};
} // namespace server
} // namespace rest
#endif // __REST_HTTP__HTTP_SERVER_H__
Loading…
Cancel
Save

Powered by TurnKey Linux.