implement HTTP client;

pull/19/head
Bryan Biedenkapp 3 years ago
parent aff2ab1e07
commit 58187c9f5f

@ -118,6 +118,11 @@ file(GLOB dvmhost_SRC
file(GLOB dvmcmd_SRC file(GLOB dvmcmd_SRC
"network/UDPSocket.h" "network/UDPSocket.h"
"network/UDPSocket.cpp" "network/UDPSocket.cpp"
"network/json/*.h"
"network/rest/*.h"
"network/rest/*.cpp"
"network/rest/http/*.h"
"network/rest/http/*.cpp"
"remote/*.h" "remote/*.h"
"remote/*.cpp" "remote/*.cpp"
"edac/SHA256.h" "edac/SHA256.h"

@ -236,6 +236,17 @@ inline std::string strtolower(const std::string value) {
return v; return v;
} }
/// <summary>
/// Helper to upper-case an input string.
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
inline std::string strtoupper(const std::string value) {
std::string v = value;
std::transform(v.begin(), v.end(), v.begin(), ::toupper);
return v;
}
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Macros // Macros
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------

@ -70,7 +70,7 @@ using namespace modem;
/// <param name="obj"></param> /// <param name="obj"></param>
void setResponseDefaultStatus(json::object& obj) void setResponseDefaultStatus(json::object& obj)
{ {
int s = (int)HTTPReply::OK; int s = (int)HTTPPayload::OK;
obj["status"].set<int>(s); obj["status"].set<int>(s);
} }
@ -80,9 +80,9 @@ void setResponseDefaultStatus(json::object& obj)
/// <param name="reply"></param> /// <param name="reply"></param>
/// <param name="message"></param> /// <param name="message"></param>
/// <param name="status"></param> /// <param name="status"></param>
void errorReply(HTTPReply& reply, std::string message, HTTPReply::StatusType status = HTTPReply::BAD_REQUEST) void errorPayload(HTTPPayload& reply, std::string message, HTTPPayload::StatusType status = HTTPPayload::BAD_REQUEST)
{ {
HTTPReply rep; HTTPPayload rep;
rep.status = status; rep.status = status;
json::object response = json::object(); json::object response = json::object();
@ -91,7 +91,7 @@ void errorReply(HTTPReply& reply, std::string message, HTTPReply::StatusType sta
response["status"].set<int>(s); response["status"].set<int>(s);
response["message"].set<std::string>(message); response["message"].set<std::string>(message);
reply.reply(response); reply.payload(response);
} }
/// <summary> /// <summary>
@ -101,11 +101,11 @@ void errorReply(HTTPReply& reply, std::string message, HTTPReply::StatusType sta
/// <param name="reply"></param> /// <param name="reply"></param>
/// <param name="obj"></param> /// <param name="obj"></param>
/// <returns></returns> /// <returns></returns>
bool parseRequestBody(const HTTPRequest& request, HTTPReply& reply, json::object& obj) bool parseRequestBody(const HTTPPayload& request, HTTPPayload& reply, json::object& obj)
{ {
std::string contentType = request.headers.find("Content-Type"); std::string contentType = request.headers.find("Content-Type");
if (contentType != "application/json") { if (contentType != "application/json") {
reply = HTTPReply::stockReply(HTTPReply::BAD_REQUEST, "application/json"); reply = HTTPPayload::statusPayload(HTTPPayload::BAD_REQUEST, "application/json");
return false; return false;
} }
@ -113,13 +113,13 @@ bool parseRequestBody(const HTTPRequest& request, HTTPReply& reply, json::object
json::value v; json::value v;
std::string err = json::parse(v, request.content); std::string err = json::parse(v, request.content);
if (!err.empty()) { if (!err.empty()) {
errorReply(reply, err); errorPayload(reply, err);
return false; return false;
} }
// ensure parsed JSON is an object // ensure parsed JSON is an object
if (!v.is<json::object>()) { if (!v.is<json::object>()) {
errorReply(reply, "Request was not a valid JSON object."); errorPayload(reply, "Request was not a valid JSON object.");
return false; return false;
} }
@ -313,12 +313,12 @@ void RESTAPI::invalidateHostToken(const std::string host)
/// ///
/// </summary> /// </summary>
/// <param name="request"></param> /// <param name="request"></param>
bool RESTAPI::validateAuth(const HTTPRequest& request, HTTPReply& reply) bool RESTAPI::validateAuth(const HTTPPayload& request, HTTPPayload& reply)
{ {
std::string host = request.headers.find("Host"); std::string host = request.headers.find("Host");
std::string headerToken = request.headers.find("X-DVM-Auth-Token"); std::string headerToken = request.headers.find("X-DVM-Auth-Token");
if (headerToken == "") { if (headerToken == "") {
errorReply(reply, "invalid authentication token", HTTPReply::UNAUTHORIZED); errorPayload(reply, "invalid authentication token", HTTPPayload::UNAUTHORIZED);
return false; return false;
} }
@ -330,12 +330,12 @@ bool RESTAPI::validateAuth(const HTTPRequest& request, HTTPReply& reply)
return true; return true;
} else { } else {
m_authTokens.erase(host); // devalidate host m_authTokens.erase(host); // devalidate host
errorReply(reply, "invalid authentication token", HTTPReply::UNAUTHORIZED); errorPayload(reply, "invalid authentication token", HTTPPayload::UNAUTHORIZED);
return false; return false;
} }
} }
else { else {
errorReply(reply, "invalid authentication token", HTTPReply::UNAUTHORIZED); errorPayload(reply, "invalid authentication token", HTTPPayload::UNAUTHORIZED);
return false; return false;
} }
@ -348,7 +348,7 @@ bool RESTAPI::validateAuth(const HTTPRequest& request, HTTPReply& reply)
/// <param name="request"></param> /// <param name="request"></param>
/// <param name="reply"></param> /// <param name="reply"></param>
/// <param name="match"></param> /// <param name="match"></param>
void RESTAPI::restAPI_PutAuth(const HTTPRequest& request, HTTPReply& reply, const RequestMatch& match) void RESTAPI::restAPI_PutAuth(const HTTPPayload& request, HTTPPayload& reply, const RequestMatch& match)
{ {
std::string host = request.headers.find("Host"); std::string host = request.headers.find("Host");
json::object response = json::object(); json::object response = json::object();
@ -362,26 +362,26 @@ void RESTAPI::restAPI_PutAuth(const HTTPRequest& request, HTTPReply& reply, cons
// validate auth is a string within the JSON blob // validate auth is a string within the JSON blob
if (!req["auth"].is<std::string>()) { if (!req["auth"].is<std::string>()) {
invalidateHostToken(host); invalidateHostToken(host);
errorReply(reply, "password was not a valid string"); errorPayload(reply, "password was not a valid string");
return; return;
} }
std::string auth = req["auth"].get<std::string>(); std::string auth = req["auth"].get<std::string>();
if (auth.empty()) { if (auth.empty()) {
invalidateHostToken(host); invalidateHostToken(host);
errorReply(reply, "auth cannot be empty"); errorPayload(reply, "auth cannot be empty");
return; return;
} }
if (auth.size() > 64) { if (auth.size() > 64) {
invalidateHostToken(host); invalidateHostToken(host);
errorReply(reply, "auth cannot be longer than 64 characters"); errorPayload(reply, "auth cannot be longer than 64 characters");
return; return;
} }
if (!(auth.find_first_not_of("0123456789abcdefABCDEF", 2) == std::string::npos)) { if (!(auth.find_first_not_of("0123456789abcdefABCDEF", 2) == std::string::npos)) {
invalidateHostToken(host); invalidateHostToken(host);
errorReply(reply, "auth contains invalid characters"); errorPayload(reply, "auth contains invalid characters");
return; return;
} }
@ -406,7 +406,7 @@ void RESTAPI::restAPI_PutAuth(const HTTPRequest& request, HTTPReply& reply, cons
// compare hashes // compare hashes
if (::memcmp(m_passwordHash, passwordHash, 32U) != 0) { if (::memcmp(m_passwordHash, passwordHash, 32U) != 0) {
invalidateHostToken(host); invalidateHostToken(host);
errorReply(reply, "invalid password"); errorPayload(reply, "invalid password");
return; return;
} }
@ -418,7 +418,7 @@ void RESTAPI::restAPI_PutAuth(const HTTPRequest& request, HTTPReply& reply, cons
m_authTokens[host] = salt; m_authTokens[host] = salt;
response["token"].set<std::string>(std::to_string(salt)); response["token"].set<std::string>(std::to_string(salt));
reply.reply(response); reply.payload(response);
} }
/// <summary> /// <summary>
@ -427,7 +427,7 @@ void RESTAPI::restAPI_PutAuth(const HTTPRequest& request, HTTPReply& reply, cons
/// <param name="request"></param> /// <param name="request"></param>
/// <param name="reply"></param> /// <param name="reply"></param>
/// <param name="match"></param> /// <param name="match"></param>
void RESTAPI::restAPI_GetVersion(const HTTPRequest& request, HTTPReply& reply, const RequestMatch& match) void RESTAPI::restAPI_GetVersion(const HTTPPayload& request, HTTPPayload& reply, const RequestMatch& match)
{ {
if (!validateAuth(request, reply)) { if (!validateAuth(request, reply)) {
return; return;
@ -437,7 +437,7 @@ void RESTAPI::restAPI_GetVersion(const HTTPRequest& request, HTTPReply& reply, c
setResponseDefaultStatus(response); setResponseDefaultStatus(response);
response["version"].set<std::string>(std::string((__PROG_NAME__ " " __VER__ " (" DESCR_DMR DESCR_P25 DESCR_NXDN "CW Id, Network) (built " __BUILD__ ")"))); response["version"].set<std::string>(std::string((__PROG_NAME__ " " __VER__ " (" DESCR_DMR DESCR_P25 DESCR_NXDN "CW Id, Network) (built " __BUILD__ ")")));
reply.reply(response); reply.payload(response);
} }
/// <summary> /// <summary>
@ -446,7 +446,7 @@ void RESTAPI::restAPI_GetVersion(const HTTPRequest& request, HTTPReply& reply, c
/// <param name="request"></param> /// <param name="request"></param>
/// <param name="reply"></param> /// <param name="reply"></param>
/// <param name="match"></param> /// <param name="match"></param>
void RESTAPI::restAPI_GetStatus(const HTTPRequest& request, HTTPReply& reply, const RequestMatch& match) void RESTAPI::restAPI_GetStatus(const HTTPPayload& request, HTTPPayload& reply, const RequestMatch& match)
{ {
if (!validateAuth(request, reply)) { if (!validateAuth(request, reply)) {
return; return;
@ -563,7 +563,7 @@ void RESTAPI::restAPI_GetStatus(const HTTPRequest& request, HTTPReply& reply, co
response["modem"].set<json::object>(modemInfo); response["modem"].set<json::object>(modemInfo);
} }
reply.reply(response); reply.payload(response);
} }
/// <summary> /// <summary>
@ -572,7 +572,7 @@ void RESTAPI::restAPI_GetStatus(const HTTPRequest& request, HTTPReply& reply, co
/// <param name="request"></param> /// <param name="request"></param>
/// <param name="reply"></param> /// <param name="reply"></param>
/// <param name="match"></param> /// <param name="match"></param>
void RESTAPI::restAPI_GetVoiceCh(const HTTPRequest& request, HTTPReply& reply, const RequestMatch& match) void RESTAPI::restAPI_GetVoiceCh(const HTTPPayload& request, HTTPPayload& reply, const RequestMatch& match)
{ {
if (!validateAuth(request, reply)) { if (!validateAuth(request, reply)) {
return; return;
@ -599,7 +599,7 @@ void RESTAPI::restAPI_GetVoiceCh(const HTTPRequest& request, HTTPReply& reply, c
} }
response["channels"].set<json::array>(channels); response["channels"].set<json::array>(channels);
reply.reply(response); reply.payload(response);
} }
/// <summary> /// <summary>
@ -608,7 +608,7 @@ void RESTAPI::restAPI_GetVoiceCh(const HTTPRequest& request, HTTPReply& reply, c
/// <param name="request"></param> /// <param name="request"></param>
/// <param name="reply"></param> /// <param name="reply"></param>
/// <param name="match"></param> /// <param name="match"></param>
void RESTAPI::restAPI_PutModemMode(const HTTPRequest& request, HTTPReply& reply, const RequestMatch& match) void RESTAPI::restAPI_PutModemMode(const HTTPPayload& request, HTTPPayload& reply, const RequestMatch& match)
{ {
if (!validateAuth(request, reply)) { if (!validateAuth(request, reply)) {
return; return;
@ -624,7 +624,7 @@ void RESTAPI::restAPI_PutModemMode(const HTTPRequest& request, HTTPReply& reply,
// validate mode is a string within the JSON blob // validate mode is a string within the JSON blob
if (!req["mode"].is<std::string>()) { if (!req["mode"].is<std::string>()) {
errorReply(reply, "password was not a valid string"); errorPayload(reply, "password was not a valid string");
return; return;
} }
@ -638,7 +638,7 @@ void RESTAPI::restAPI_PutModemMode(const HTTPRequest& request, HTTPReply& reply,
uint8_t hostMode = m_host->m_state; uint8_t hostMode = m_host->m_state;
response["mode"].set<uint8_t>(hostMode); response["mode"].set<uint8_t>(hostMode);
reply.reply(response); reply.payload(response);
} }
else if (mode == MODE_OPT_LCKOUT) { else if (mode == MODE_OPT_LCKOUT) {
m_host->m_fixedMode = false; m_host->m_fixedMode = false;
@ -648,7 +648,7 @@ void RESTAPI::restAPI_PutModemMode(const HTTPRequest& request, HTTPReply& reply,
uint8_t hostMode = m_host->m_state; uint8_t hostMode = m_host->m_state;
response["mode"].set<uint8_t>(hostMode); response["mode"].set<uint8_t>(hostMode);
reply.reply(response); reply.payload(response);
} }
#if defined(ENABLE_DMR) #if defined(ENABLE_DMR)
else if (mode == MODE_OPT_FDMR) { else if (mode == MODE_OPT_FDMR) {
@ -660,10 +660,10 @@ void RESTAPI::restAPI_PutModemMode(const HTTPRequest& request, HTTPReply& reply,
uint8_t hostMode = m_host->m_state; uint8_t hostMode = m_host->m_state;
response["mode"].set<uint8_t>(hostMode); response["mode"].set<uint8_t>(hostMode);
reply.reply(response); reply.payload(response);
} }
else { else {
errorReply(reply, "DMR mode is not enabled", HTTPReply::SERVICE_UNAVAILABLE); errorPayload(reply, "DMR mode is not enabled", HTTPPayload::SERVICE_UNAVAILABLE);
} }
} }
#endif // defined(ENABLE_DMR) #endif // defined(ENABLE_DMR)
@ -677,10 +677,10 @@ void RESTAPI::restAPI_PutModemMode(const HTTPRequest& request, HTTPReply& reply,
uint8_t hostMode = m_host->m_state; uint8_t hostMode = m_host->m_state;
response["mode"].set<uint8_t>(hostMode); response["mode"].set<uint8_t>(hostMode);
reply.reply(response); reply.payload(response);
} }
else { else {
errorReply(reply, "P25 mode is not enabled", HTTPReply::SERVICE_UNAVAILABLE); errorPayload(reply, "P25 mode is not enabled", HTTPPayload::SERVICE_UNAVAILABLE);
} }
} }
#endif // defined(ENABLE_P25) #endif // defined(ENABLE_P25)
@ -694,15 +694,15 @@ void RESTAPI::restAPI_PutModemMode(const HTTPRequest& request, HTTPReply& reply,
uint8_t hostMode = m_host->m_state; uint8_t hostMode = m_host->m_state;
response["mode"].set<uint8_t>(hostMode); response["mode"].set<uint8_t>(hostMode);
reply.reply(response); reply.payload(response);
} }
else { else {
errorReply(reply, "NXDN mode is not enabled", HTTPReply::SERVICE_UNAVAILABLE); errorPayload(reply, "NXDN mode is not enabled", HTTPPayload::SERVICE_UNAVAILABLE);
} }
} }
#endif // defined(ENABLE_NXDN) #endif // defined(ENABLE_NXDN)
else { else {
errorReply(reply, "invalid mode"); errorPayload(reply, "invalid mode");
} }
} }
@ -712,7 +712,7 @@ void RESTAPI::restAPI_PutModemMode(const HTTPRequest& request, HTTPReply& reply,
/// <param name="request"></param> /// <param name="request"></param>
/// <param name="reply"></param> /// <param name="reply"></param>
/// <param name="match"></param> /// <param name="match"></param>
void RESTAPI::restAPI_PutModemKill(const HTTPRequest& request, HTTPReply& reply, const RequestMatch& match) void RESTAPI::restAPI_PutModemKill(const HTTPPayload& request, HTTPPayload& reply, const RequestMatch& match)
{ {
if (!validateAuth(request, reply)) { if (!validateAuth(request, reply)) {
return; return;
@ -723,7 +723,7 @@ void RESTAPI::restAPI_PutModemKill(const HTTPRequest& request, HTTPReply& reply,
return; return;
} }
errorReply(reply, "OK", HTTPReply::OK); errorPayload(reply, "OK", HTTPPayload::OK);
if (!req["force"].is<bool>()) { if (!req["force"].is<bool>()) {
g_killed = true; g_killed = true;
@ -744,7 +744,7 @@ void RESTAPI::restAPI_PutModemKill(const HTTPRequest& request, HTTPReply& reply,
/// <param name="request"></param> /// <param name="request"></param>
/// <param name="reply"></param> /// <param name="reply"></param>
/// <param name="match"></param> /// <param name="match"></param>
void RESTAPI::restAPI_PutPermitTG(const HTTPRequest& request, HTTPReply& reply, const RequestMatch& match) void RESTAPI::restAPI_PutPermitTG(const HTTPPayload& request, HTTPPayload& reply, const RequestMatch& match)
{ {
if (!validateAuth(request, reply)) { if (!validateAuth(request, reply)) {
return; return;
@ -755,16 +755,16 @@ void RESTAPI::restAPI_PutPermitTG(const HTTPRequest& request, HTTPReply& reply,
return; return;
} }
errorReply(reply, "OK", HTTPReply::OK); errorPayload(reply, "OK", HTTPPayload::OK);
if (!m_host->m_authoritative) { if (!m_host->m_authoritative) {
errorReply(reply, "Host is authoritative, cannot permit TG"); errorPayload(reply, "Host is authoritative, cannot permit TG");
return; return;
} }
// validate state is a string within the JSON blob // validate state is a string within the JSON blob
if (!req["state"].is<int>()) { if (!req["state"].is<int>()) {
errorReply(reply, "state was not a valid integer"); errorPayload(reply, "state was not a valid integer");
return; return;
} }
@ -772,14 +772,14 @@ void RESTAPI::restAPI_PutPermitTG(const HTTPRequest& request, HTTPReply& reply,
// validate destination ID is a integer within the JSON blob // validate destination ID is a integer within the JSON blob
if (!req["dstId"].is<int>()) { if (!req["dstId"].is<int>()) {
errorReply(reply, "destination ID was not a valid integer"); errorPayload(reply, "destination ID was not a valid integer");
return; return;
} }
uint32_t dstId = req["dstId"].get<uint32_t>(); uint32_t dstId = req["dstId"].get<uint32_t>();
if (dstId == 0U) { if (dstId == 0U) {
errorReply(reply, "destination ID is an illegal TGID"); errorPayload(reply, "destination ID is an illegal TGID");
return; return;
} }
@ -789,14 +789,14 @@ void RESTAPI::restAPI_PutPermitTG(const HTTPRequest& request, HTTPReply& reply,
{ {
// validate slot is a integer within the JSON blob // validate slot is a integer within the JSON blob
if (!req["slot"].is<int>()) { if (!req["slot"].is<int>()) {
errorReply(reply, "slot was not a valid integer"); errorPayload(reply, "slot was not a valid integer");
return; return;
} }
uint8_t slot = (uint8_t)req["slot"].get<int>(); uint8_t slot = (uint8_t)req["slot"].get<int>();
if (slot == 0U || slot > 2U) { if (slot == 0U || slot > 2U) {
errorReply(reply, "illegal DMR slot"); errorPayload(reply, "illegal DMR slot");
return; return;
} }
@ -804,12 +804,12 @@ void RESTAPI::restAPI_PutPermitTG(const HTTPRequest& request, HTTPReply& reply,
m_dmr->permittedTG(dstId, slot); m_dmr->permittedTG(dstId, slot);
} }
else { else {
errorReply(reply, "DMR mode is not enabled", HTTPReply::SERVICE_UNAVAILABLE); errorPayload(reply, "DMR mode is not enabled", HTTPPayload::SERVICE_UNAVAILABLE);
} }
} }
#else #else
{ {
errorReply(reply, "DMR operations are unavailable", HTTPReply::SERVICE_UNAVAILABLE); errorPayload(reply, "DMR operations are unavailable", HTTPPayload::SERVICE_UNAVAILABLE);
} }
#endif // defined(ENABLE_DMR) #endif // defined(ENABLE_DMR)
break; break;
@ -820,12 +820,12 @@ void RESTAPI::restAPI_PutPermitTG(const HTTPRequest& request, HTTPReply& reply,
m_p25->permittedTG(dstId); m_p25->permittedTG(dstId);
} }
else { else {
errorReply(reply, "P25 mode is not enabled", HTTPReply::SERVICE_UNAVAILABLE); errorPayload(reply, "P25 mode is not enabled", HTTPPayload::SERVICE_UNAVAILABLE);
} }
} }
#else #else
{ {
errorReply(reply, "P25 operations are unavailable", HTTPReply::SERVICE_UNAVAILABLE); errorPayload(reply, "P25 operations are unavailable", HTTPPayload::SERVICE_UNAVAILABLE);
} }
#endif // defined(ENABLE_P25) #endif // defined(ENABLE_P25)
break; break;
@ -836,17 +836,17 @@ void RESTAPI::restAPI_PutPermitTG(const HTTPRequest& request, HTTPReply& reply,
m_nxdn->permittedTG(dstId); m_nxdn->permittedTG(dstId);
} }
else { else {
errorReply(reply, "NXDN mode is not enabled", HTTPReply::SERVICE_UNAVAILABLE); errorPayload(reply, "NXDN mode is not enabled", HTTPPayload::SERVICE_UNAVAILABLE);
} }
} }
#else #else
{ {
errorReply(reply, "NXDN operations are unavailable", HTTPReply::SERVICE_UNAVAILABLE); errorPayload(reply, "NXDN operations are unavailable", HTTPPayload::SERVICE_UNAVAILABLE);
} }
#endif // defined(ENABLE_NXDN) #endif // defined(ENABLE_NXDN)
break; break;
default: default:
errorReply(reply, "invalid mode"); errorPayload(reply, "invalid mode");
break; break;
} }
} }
@ -857,7 +857,7 @@ void RESTAPI::restAPI_PutPermitTG(const HTTPRequest& request, HTTPReply& reply,
/// <param name="request"></param> /// <param name="request"></param>
/// <param name="reply"></param> /// <param name="reply"></param>
/// <param name="match"></param> /// <param name="match"></param>
void RESTAPI::restAPI_PutGrantTG(const HTTPRequest& request, HTTPReply& reply, const RequestMatch& match) void RESTAPI::restAPI_PutGrantTG(const HTTPPayload& request, HTTPPayload& reply, const RequestMatch& match)
{ {
if (!validateAuth(request, reply)) { if (!validateAuth(request, reply)) {
return; return;
@ -868,16 +868,16 @@ void RESTAPI::restAPI_PutGrantTG(const HTTPRequest& request, HTTPReply& reply, c
return; return;
} }
errorReply(reply, "OK", HTTPReply::OK); errorPayload(reply, "OK", HTTPPayload::OK);
if (m_host->m_authoritative && (m_host->m_dmrCtrlChannel || m_host->m_p25CtrlChannel || m_host->m_nxdnCtrlChannel)) { if (m_host->m_authoritative && (m_host->m_dmrCtrlChannel || m_host->m_p25CtrlChannel || m_host->m_nxdnCtrlChannel)) {
errorReply(reply, "Host is authoritative, cannot grant TG"); errorPayload(reply, "Host is authoritative, cannot grant TG");
return; return;
} }
// validate state is a string within the JSON blob // validate state is a string within the JSON blob
if (!req["state"].is<int>()) { if (!req["state"].is<int>()) {
errorReply(reply, "state was not a valid integer"); errorPayload(reply, "state was not a valid integer");
return; return;
} }
@ -885,27 +885,27 @@ void RESTAPI::restAPI_PutGrantTG(const HTTPRequest& request, HTTPReply& reply, c
// validate destination ID is a integer within the JSON blob // validate destination ID is a integer within the JSON blob
if (!req["dstId"].is<int>()) { if (!req["dstId"].is<int>()) {
errorReply(reply, "destination ID was not a valid integer"); errorPayload(reply, "destination ID was not a valid integer");
return; return;
} }
uint32_t dstId = req["dstId"].get<uint32_t>(); uint32_t dstId = req["dstId"].get<uint32_t>();
if (dstId == 0U) { if (dstId == 0U) {
errorReply(reply, "destination ID is an illegal TGID"); errorPayload(reply, "destination ID is an illegal TGID");
return; return;
} }
// validate unit-to-unit is a integer within the JSON blob // validate unit-to-unit is a integer within the JSON blob
if (!req["unitToUnit"].is<int>()) { if (!req["unitToUnit"].is<int>()) {
errorReply(reply, "unit-to-unit was not a valid integer"); errorPayload(reply, "unit-to-unit was not a valid integer");
return; return;
} }
uint8_t unitToUnit = (uint8_t)req["unitToUnit"].get<int>(); uint8_t unitToUnit = (uint8_t)req["unitToUnit"].get<int>();
if (unitToUnit > 1U) { if (unitToUnit > 1U) {
errorReply(reply, "unit-to-unit must be a 0 or 1"); errorPayload(reply, "unit-to-unit must be a 0 or 1");
return; return;
} }
@ -915,14 +915,14 @@ void RESTAPI::restAPI_PutGrantTG(const HTTPRequest& request, HTTPReply& reply, c
{ {
// validate slot is a integer within the JSON blob // validate slot is a integer within the JSON blob
if (!req["slot"].is<int>()) { if (!req["slot"].is<int>()) {
errorReply(reply, "slot was not a valid integer"); errorPayload(reply, "slot was not a valid integer");
return; return;
} }
uint8_t slot = (uint8_t)req["slot"].get<int>(); uint8_t slot = (uint8_t)req["slot"].get<int>();
if (slot == 0U || slot > 2U) { if (slot == 0U || slot > 2U) {
errorReply(reply, "illegal DMR slot"); errorPayload(reply, "illegal DMR slot");
return; return;
} }
@ -931,12 +931,12 @@ void RESTAPI::restAPI_PutGrantTG(const HTTPRequest& request, HTTPReply& reply, c
//m_dmr->grantTG(dstId, slot, unitToUnit == 1U); //m_dmr->grantTG(dstId, slot, unitToUnit == 1U);
} }
else { else {
errorReply(reply, "DMR mode is not enabled", HTTPReply::SERVICE_UNAVAILABLE); errorPayload(reply, "DMR mode is not enabled", HTTPPayload::SERVICE_UNAVAILABLE);
} }
} }
#else #else
{ {
errorReply(reply, "DMR operations are unavailable", HTTPReply::SERVICE_UNAVAILABLE); errorPayload(reply, "DMR operations are unavailable", HTTPPayload::SERVICE_UNAVAILABLE);
} }
#endif // defined(ENABLE_DMR) #endif // defined(ENABLE_DMR)
break; break;
@ -948,12 +948,12 @@ void RESTAPI::restAPI_PutGrantTG(const HTTPRequest& request, HTTPReply& reply, c
//m_p25->grantTG(dstId, unitToUnit == 1U); //m_p25->grantTG(dstId, unitToUnit == 1U);
} }
else { else {
errorReply(reply, "P25 mode is not enabled", HTTPReply::SERVICE_UNAVAILABLE); errorPayload(reply, "P25 mode is not enabled", HTTPPayload::SERVICE_UNAVAILABLE);
} }
} }
#else #else
{ {
errorReply(reply, "P25 operations are unavailable", HTTPReply::SERVICE_UNAVAILABLE); errorPayload(reply, "P25 operations are unavailable", HTTPPayload::SERVICE_UNAVAILABLE);
} }
#endif // defined(ENABLE_P25) #endif // defined(ENABLE_P25)
break; break;
@ -965,17 +965,17 @@ void RESTAPI::restAPI_PutGrantTG(const HTTPRequest& request, HTTPReply& reply, c
//nxdn->grantTG(dstId, unitToUnit == 1U); //nxdn->grantTG(dstId, unitToUnit == 1U);
} }
else { else {
errorReply(reply, "NXDN mode is not enabled", HTTPReply::SERVICE_UNAVAILABLE); errorPayload(reply, "NXDN mode is not enabled", HTTPPayload::SERVICE_UNAVAILABLE);
} }
} }
#else #else
{ {
errorReply(reply, "NXDN operations are unavailable", HTTPReply::SERVICE_UNAVAILABLE); errorPayload(reply, "NXDN operations are unavailable", HTTPPayload::SERVICE_UNAVAILABLE);
} }
#endif // defined(ENABLE_NXDN) #endif // defined(ENABLE_NXDN)
break; break;
default: default:
errorReply(reply, "invalid mode"); errorPayload(reply, "invalid mode");
} }
} }
@ -985,13 +985,13 @@ void RESTAPI::restAPI_PutGrantTG(const HTTPRequest& request, HTTPReply& reply, c
/// <param name="request"></param> /// <param name="request"></param>
/// <param name="reply"></param> /// <param name="reply"></param>
/// <param name="match"></param> /// <param name="match"></param>
void RESTAPI::restAPI_GetReleaseGrants(const HTTPRequest& request, HTTPReply& reply, const RequestMatch& match) void RESTAPI::restAPI_GetReleaseGrants(const HTTPPayload& request, HTTPPayload& reply, const RequestMatch& match)
{ {
if (!validateAuth(request, reply)) { if (!validateAuth(request, reply)) {
return; return;
} }
errorReply(reply, "OK", HTTPReply::OK); errorPayload(reply, "OK", HTTPPayload::OK);
#if defined(ENABLE_DMR) #if defined(ENABLE_DMR)
if (m_dmr != nullptr) { if (m_dmr != nullptr) {
m_dmr->affiliations().releaseGrant(0, true); m_dmr->affiliations().releaseGrant(0, true);
@ -1015,13 +1015,13 @@ void RESTAPI::restAPI_GetReleaseGrants(const HTTPRequest& request, HTTPReply& re
/// <param name="request"></param> /// <param name="request"></param>
/// <param name="reply"></param> /// <param name="reply"></param>
/// <param name="match"></param> /// <param name="match"></param>
void RESTAPI::restAPI_GetReleaseAffs(const HTTPRequest& request, HTTPReply& reply, const RequestMatch& match) void RESTAPI::restAPI_GetReleaseAffs(const HTTPPayload& request, HTTPPayload& reply, const RequestMatch& match)
{ {
if (!validateAuth(request, reply)) { if (!validateAuth(request, reply)) {
return; return;
} }
errorReply(reply, "OK", HTTPReply::OK); errorPayload(reply, "OK", HTTPPayload::OK);
#if defined(ENABLE_DMR) #if defined(ENABLE_DMR)
if (m_dmr != nullptr) { if (m_dmr != nullptr) {
m_dmr->affiliations().clearGroupAff(0, true); m_dmr->affiliations().clearGroupAff(0, true);
@ -1045,25 +1045,25 @@ void RESTAPI::restAPI_GetReleaseAffs(const HTTPRequest& request, HTTPReply& repl
/// <param name="request"></param> /// <param name="request"></param>
/// <param name="reply"></param> /// <param name="reply"></param>
/// <param name="match"></param> /// <param name="match"></param>
void RESTAPI::restAPI_GetRIDWhitelist(const HTTPRequest& request, HTTPReply& reply, const RequestMatch& match) void RESTAPI::restAPI_GetRIDWhitelist(const HTTPPayload& request, HTTPPayload& reply, const RequestMatch& match)
{ {
if (!validateAuth(request, reply)) { if (!validateAuth(request, reply)) {
return; return;
} }
if (match.size() < 2) { if (match.size() < 2) {
errorReply(reply, "invalid API call arguments"); errorPayload(reply, "invalid API call arguments");
return; return;
} }
errorReply(reply, "OK", HTTPReply::OK); errorPayload(reply, "OK", HTTPPayload::OK);
uint32_t srcId = (uint32_t)::strtoul(match.str(1).c_str(), NULL, 10); uint32_t srcId = (uint32_t)::strtoul(match.str(1).c_str(), NULL, 10);
if (srcId != 0U) { if (srcId != 0U) {
m_ridLookup->toggleEntry(srcId, true); m_ridLookup->toggleEntry(srcId, true);
} }
else { else {
errorReply(reply, "tried to whitelist RID 0"); errorPayload(reply, "tried to whitelist RID 0");
} }
} }
@ -1073,25 +1073,25 @@ void RESTAPI::restAPI_GetRIDWhitelist(const HTTPRequest& request, HTTPReply& rep
/// <param name="request"></param> /// <param name="request"></param>
/// <param name="reply"></param> /// <param name="reply"></param>
/// <param name="match"></param> /// <param name="match"></param>
void RESTAPI::restAPI_GetRIDBlacklist(const HTTPRequest& request, HTTPReply& reply, const RequestMatch& match) void RESTAPI::restAPI_GetRIDBlacklist(const HTTPPayload& request, HTTPPayload& reply, const RequestMatch& match)
{ {
if (!validateAuth(request, reply)) { if (!validateAuth(request, reply)) {
return; return;
} }
if (match.size() < 2) { if (match.size() < 2) {
errorReply(reply, "invalid API call arguments"); errorPayload(reply, "invalid API call arguments");
return; return;
} }
errorReply(reply, "OK", HTTPReply::OK); errorPayload(reply, "OK", HTTPPayload::OK);
uint32_t srcId = (uint32_t)::strtoul(match.str(1).c_str(), NULL, 10); uint32_t srcId = (uint32_t)::strtoul(match.str(1).c_str(), NULL, 10);
if (srcId != 0U) { if (srcId != 0U) {
m_ridLookup->toggleEntry(srcId, false); m_ridLookup->toggleEntry(srcId, false);
} }
else { else {
errorReply(reply, "tried to blacklist RID 0"); errorPayload(reply, "tried to blacklist RID 0");
} }
} }
@ -1105,29 +1105,29 @@ void RESTAPI::restAPI_GetRIDBlacklist(const HTTPRequest& request, HTTPReply& rep
/// <param name="request"></param> /// <param name="request"></param>
/// <param name="reply"></param> /// <param name="reply"></param>
/// <param name="match"></param> /// <param name="match"></param>
void RESTAPI::restAPI_GetDMRBeacon(const HTTPRequest& request, HTTPReply& reply, const RequestMatch& match) void RESTAPI::restAPI_GetDMRBeacon(const HTTPPayload& request, HTTPPayload& reply, const RequestMatch& match)
{ {
if (!validateAuth(request, reply)) { if (!validateAuth(request, reply)) {
return; return;
} }
#if defined(ENABLE_DMR) #if defined(ENABLE_DMR)
errorReply(reply, "OK", HTTPReply::OK); errorPayload(reply, "OK", HTTPPayload::OK);
if (m_dmr != nullptr) { if (m_dmr != nullptr) {
if (m_host->m_dmrBeacons) { if (m_host->m_dmrBeacons) {
g_fireDMRBeacon = true; g_fireDMRBeacon = true;
} }
else { else {
errorReply(reply, "DMR beacons are not enabled", HTTPReply::SERVICE_UNAVAILABLE); errorPayload(reply, "DMR beacons are not enabled", HTTPPayload::SERVICE_UNAVAILABLE);
return; return;
} }
} }
else { else {
errorReply(reply, "DMR mode is not enabled", HTTPReply::SERVICE_UNAVAILABLE); errorPayload(reply, "DMR mode is not enabled", HTTPPayload::SERVICE_UNAVAILABLE);
return; return;
} }
#else #else
errorReply(reply, "DMR operations are unavailable", HTTPReply::SERVICE_UNAVAILABLE); errorPayload(reply, "DMR operations are unavailable", HTTPPayload::SERVICE_UNAVAILABLE);
#endif // defined(ENABLE_DMR) #endif // defined(ENABLE_DMR)
} }
@ -1137,7 +1137,7 @@ void RESTAPI::restAPI_GetDMRBeacon(const HTTPRequest& request, HTTPReply& reply,
/// <param name="request"></param> /// <param name="request"></param>
/// <param name="reply"></param> /// <param name="reply"></param>
/// <param name="match"></param> /// <param name="match"></param>
void RESTAPI::restAPI_GetDMRDebug(const HTTPRequest& request, HTTPReply& reply, const RequestMatch& match) void RESTAPI::restAPI_GetDMRDebug(const HTTPPayload& request, HTTPPayload& reply, const RequestMatch& match)
{ {
if (!validateAuth(request, reply)) { if (!validateAuth(request, reply)) {
return; return;
@ -1146,7 +1146,7 @@ void RESTAPI::restAPI_GetDMRDebug(const HTTPRequest& request, HTTPReply& reply,
json::object response = json::object(); json::object response = json::object();
setResponseDefaultStatus(response); setResponseDefaultStatus(response);
#if defined(ENABLE_DMR) #if defined(ENABLE_DMR)
errorReply(reply, "OK", HTTPReply::OK); errorPayload(reply, "OK", HTTPPayload::OK);
if (m_dmr != nullptr) { if (m_dmr != nullptr) {
if (match.size() <= 1) { if (match.size() <= 1) {
bool debug = m_dmr->getDebug(); bool debug = m_dmr->getDebug();
@ -1155,7 +1155,7 @@ void RESTAPI::restAPI_GetDMRDebug(const HTTPRequest& request, HTTPReply& reply,
response["debug"].set<bool>(debug); response["debug"].set<bool>(debug);
response["verbose"].set<bool>(verbose); response["verbose"].set<bool>(verbose);
reply.reply(response); reply.payload(response);
return; return;
} }
else { else {
@ -1167,11 +1167,11 @@ void RESTAPI::restAPI_GetDMRDebug(const HTTPRequest& request, HTTPReply& reply,
} }
} }
else { else {
errorReply(reply, "DMR mode is not enabled", HTTPReply::SERVICE_UNAVAILABLE); errorPayload(reply, "DMR mode is not enabled", HTTPPayload::SERVICE_UNAVAILABLE);
return; return;
} }
#else #else
errorReply(reply, "DMR operations are unavailable", HTTPReply::SERVICE_UNAVAILABLE); errorPayload(reply, "DMR operations are unavailable", HTTPPayload::SERVICE_UNAVAILABLE);
#endif // defined(ENABLE_DMR) #endif // defined(ENABLE_DMR)
} }
@ -1181,7 +1181,7 @@ void RESTAPI::restAPI_GetDMRDebug(const HTTPRequest& request, HTTPReply& reply,
/// <param name="request"></param> /// <param name="request"></param>
/// <param name="reply"></param> /// <param name="reply"></param>
/// <param name="match"></param> /// <param name="match"></param>
void RESTAPI::restAPI_GetDMRDumpCSBK(const HTTPRequest& request, HTTPReply& reply, const RequestMatch& match) void RESTAPI::restAPI_GetDMRDumpCSBK(const HTTPPayload& request, HTTPPayload& reply, const RequestMatch& match)
{ {
if (!validateAuth(request, reply)) { if (!validateAuth(request, reply)) {
return; return;
@ -1190,14 +1190,14 @@ void RESTAPI::restAPI_GetDMRDumpCSBK(const HTTPRequest& request, HTTPReply& repl
json::object response = json::object(); json::object response = json::object();
setResponseDefaultStatus(response); setResponseDefaultStatus(response);
#if defined(ENABLE_DMR) #if defined(ENABLE_DMR)
errorReply(reply, "OK", HTTPReply::OK); errorPayload(reply, "OK", HTTPPayload::OK);
if (m_dmr != nullptr) { if (m_dmr != nullptr) {
if (match.size() <= 1) { if (match.size() <= 1) {
bool csbkDump = m_dmr->getCSBKVerbose(); bool csbkDump = m_dmr->getCSBKVerbose();
response["verbose"].set<bool>(csbkDump); response["verbose"].set<bool>(csbkDump);
reply.reply(response); reply.payload(response);
return; return;
} }
else { else {
@ -1208,11 +1208,11 @@ void RESTAPI::restAPI_GetDMRDumpCSBK(const HTTPRequest& request, HTTPReply& repl
} }
} }
else { else {
errorReply(reply, "DMR mode is not enabled", HTTPReply::SERVICE_UNAVAILABLE); errorPayload(reply, "DMR mode is not enabled", HTTPPayload::SERVICE_UNAVAILABLE);
return; return;
} }
#else #else
errorReply(reply, "DMR operations are unavailable", HTTPReply::SERVICE_UNAVAILABLE); errorPayload(reply, "DMR operations are unavailable", HTTPPayload::SERVICE_UNAVAILABLE);
#endif // defined(ENABLE_DMR) #endif // defined(ENABLE_DMR)
} }
@ -1222,7 +1222,7 @@ void RESTAPI::restAPI_GetDMRDumpCSBK(const HTTPRequest& request, HTTPReply& repl
/// <param name="request"></param> /// <param name="request"></param>
/// <param name="reply"></param> /// <param name="reply"></param>
/// <param name="match"></param> /// <param name="match"></param>
void RESTAPI::restAPI_PutDMRRID(const HTTPRequest& request, HTTPReply& reply, const RequestMatch& match) void RESTAPI::restAPI_PutDMRRID(const HTTPPayload& request, HTTPPayload& reply, const RequestMatch& match)
{ {
if (!validateAuth(request, reply)) { if (!validateAuth(request, reply)) {
return; return;
@ -1233,7 +1233,7 @@ void RESTAPI::restAPI_PutDMRRID(const HTTPRequest& request, HTTPReply& reply, co
return; return;
} }
errorReply(reply, "OK", HTTPReply::OK); errorPayload(reply, "OK", HTTPPayload::OK);
} }
/// <summary> /// <summary>
@ -1242,18 +1242,18 @@ void RESTAPI::restAPI_PutDMRRID(const HTTPRequest& request, HTTPReply& reply, co
/// <param name="request"></param> /// <param name="request"></param>
/// <param name="reply"></param> /// <param name="reply"></param>
/// <param name="match"></param> /// <param name="match"></param>
void RESTAPI::restAPI_GetDMRCCEnable(const HTTPRequest& request, HTTPReply& reply, const RequestMatch& match) void RESTAPI::restAPI_GetDMRCCEnable(const HTTPPayload& request, HTTPPayload& reply, const RequestMatch& match)
{ {
if (!validateAuth(request, reply)) { if (!validateAuth(request, reply)) {
return; return;
} }
if (match.size() < 2) { if (match.size() < 2) {
errorReply(reply, "invalid API call arguments"); errorPayload(reply, "invalid API call arguments");
return; return;
} }
errorReply(reply, "OK", HTTPReply::OK); errorPayload(reply, "OK", HTTPPayload::OK);
} }
/// <summary> /// <summary>
@ -1262,18 +1262,18 @@ void RESTAPI::restAPI_GetDMRCCEnable(const HTTPRequest& request, HTTPReply& repl
/// <param name="request"></param> /// <param name="request"></param>
/// <param name="reply"></param> /// <param name="reply"></param>
/// <param name="match"></param> /// <param name="match"></param>
void RESTAPI::restAPI_GetDMRCCBroadcast(const HTTPRequest& request, HTTPReply& reply, const RequestMatch& match) void RESTAPI::restAPI_GetDMRCCBroadcast(const HTTPPayload& request, HTTPPayload& reply, const RequestMatch& match)
{ {
if (!validateAuth(request, reply)) { if (!validateAuth(request, reply)) {
return; return;
} }
if (match.size() < 2) { if (match.size() < 2) {
errorReply(reply, "invalid API call arguments"); errorPayload(reply, "invalid API call arguments");
return; return;
} }
errorReply(reply, "OK", HTTPReply::OK); errorPayload(reply, "OK", HTTPPayload::OK);
} }
/* /*
@ -1286,29 +1286,29 @@ void RESTAPI::restAPI_GetDMRCCBroadcast(const HTTPRequest& request, HTTPReply& r
/// <param name="request"></param> /// <param name="request"></param>
/// <param name="reply"></param> /// <param name="reply"></param>
/// <param name="match"></param> /// <param name="match"></param>
void RESTAPI::restAPI_GetP25CC(const HTTPRequest& request, HTTPReply& reply, const RequestMatch& match) void RESTAPI::restAPI_GetP25CC(const HTTPPayload& request, HTTPPayload& reply, const RequestMatch& match)
{ {
if (!validateAuth(request, reply)) { if (!validateAuth(request, reply)) {
return; return;
} }
#if defined(ENABLE_P25) #if defined(ENABLE_P25)
errorReply(reply, "OK", HTTPReply::OK); errorPayload(reply, "OK", HTTPPayload::OK);
if (m_p25 != nullptr) { if (m_p25 != nullptr) {
if (m_host->m_p25CCData) { if (m_host->m_p25CCData) {
g_fireP25Control = true; g_fireP25Control = true;
} }
else { else {
errorReply(reply, "P25 control data is not enabled", HTTPReply::SERVICE_UNAVAILABLE); errorPayload(reply, "P25 control data is not enabled", HTTPPayload::SERVICE_UNAVAILABLE);
return; return;
} }
} }
else { else {
errorReply(reply, "P25 mode is not enabled", HTTPReply::SERVICE_UNAVAILABLE); errorPayload(reply, "P25 mode is not enabled", HTTPPayload::SERVICE_UNAVAILABLE);
return; return;
} }
#else #else
errorReply(reply, "P25 operations are unavailable", HTTPReply::SERVICE_UNAVAILABLE); errorPayload(reply, "P25 operations are unavailable", HTTPPayload::SERVICE_UNAVAILABLE);
#endif // defined(ENABLE_P25) #endif // defined(ENABLE_P25)
} }
@ -1318,7 +1318,7 @@ void RESTAPI::restAPI_GetP25CC(const HTTPRequest& request, HTTPReply& reply, con
/// <param name="request"></param> /// <param name="request"></param>
/// <param name="reply"></param> /// <param name="reply"></param>
/// <param name="match"></param> /// <param name="match"></param>
void RESTAPI::restAPI_GetP25Debug(const HTTPRequest& request, HTTPReply& reply, const RequestMatch& match) void RESTAPI::restAPI_GetP25Debug(const HTTPPayload& request, HTTPPayload& reply, const RequestMatch& match)
{ {
if (!validateAuth(request, reply)) { if (!validateAuth(request, reply)) {
return; return;
@ -1327,7 +1327,7 @@ void RESTAPI::restAPI_GetP25Debug(const HTTPRequest& request, HTTPReply& reply,
json::object response = json::object(); json::object response = json::object();
setResponseDefaultStatus(response); setResponseDefaultStatus(response);
#if defined(ENABLE_P25) #if defined(ENABLE_P25)
errorReply(reply, "OK", HTTPReply::OK); errorPayload(reply, "OK", HTTPPayload::OK);
if (m_dmr != nullptr) { if (m_dmr != nullptr) {
if (match.size() <= 1) { if (match.size() <= 1) {
bool debug = m_p25->getDebug(); bool debug = m_p25->getDebug();
@ -1336,7 +1336,7 @@ void RESTAPI::restAPI_GetP25Debug(const HTTPRequest& request, HTTPReply& reply,
response["debug"].set<bool>(debug); response["debug"].set<bool>(debug);
response["verbose"].set<bool>(verbose); response["verbose"].set<bool>(verbose);
reply.reply(response); reply.payload(response);
return; return;
} }
else { else {
@ -1348,11 +1348,11 @@ void RESTAPI::restAPI_GetP25Debug(const HTTPRequest& request, HTTPReply& reply,
} }
} }
else { else {
errorReply(reply, "P25 mode is not enabled", HTTPReply::SERVICE_UNAVAILABLE); errorPayload(reply, "P25 mode is not enabled", HTTPPayload::SERVICE_UNAVAILABLE);
return; return;
} }
#else #else
errorReply(reply, "P25 operations are unavailable", HTTPReply::SERVICE_UNAVAILABLE); errorPayload(reply, "P25 operations are unavailable", HTTPPayload::SERVICE_UNAVAILABLE);
#endif // defined(ENABLE_P25) #endif // defined(ENABLE_P25)
} }
@ -1362,7 +1362,7 @@ void RESTAPI::restAPI_GetP25Debug(const HTTPRequest& request, HTTPReply& reply,
/// <param name="request"></param> /// <param name="request"></param>
/// <param name="reply"></param> /// <param name="reply"></param>
/// <param name="match"></param> /// <param name="match"></param>
void RESTAPI::restAPI_GetP25DumpTSBK(const HTTPRequest& request, HTTPReply& reply, const RequestMatch& match) void RESTAPI::restAPI_GetP25DumpTSBK(const HTTPPayload& request, HTTPPayload& reply, const RequestMatch& match)
{ {
if (!validateAuth(request, reply)) { if (!validateAuth(request, reply)) {
return; return;
@ -1371,14 +1371,14 @@ void RESTAPI::restAPI_GetP25DumpTSBK(const HTTPRequest& request, HTTPReply& repl
json::object response = json::object(); json::object response = json::object();
setResponseDefaultStatus(response); setResponseDefaultStatus(response);
#if defined(ENABLE_P25) #if defined(ENABLE_P25)
errorReply(reply, "OK", HTTPReply::OK); errorPayload(reply, "OK", HTTPPayload::OK);
if (m_p25 != nullptr) { if (m_p25 != nullptr) {
if (match.size() <= 1) { if (match.size() <= 1) {
bool tsbkDump = m_p25->trunk()->getTSBKVerbose(); bool tsbkDump = m_p25->trunk()->getTSBKVerbose();
response["verbose"].set<bool>(tsbkDump); response["verbose"].set<bool>(tsbkDump);
reply.reply(response); reply.payload(response);
return; return;
} }
else { else {
@ -1389,11 +1389,11 @@ void RESTAPI::restAPI_GetP25DumpTSBK(const HTTPRequest& request, HTTPReply& repl
} }
} }
else { else {
errorReply(reply, "P25 mode is not enabled", HTTPReply::SERVICE_UNAVAILABLE); errorPayload(reply, "P25 mode is not enabled", HTTPPayload::SERVICE_UNAVAILABLE);
return; return;
} }
#else #else
errorReply(reply, "P25 operations are unavailable", HTTPReply::SERVICE_UNAVAILABLE); errorPayload(reply, "P25 operations are unavailable", HTTPPayload::SERVICE_UNAVAILABLE);
#endif // defined(ENABLE_P25) #endif // defined(ENABLE_P25)
} }
@ -1403,7 +1403,7 @@ void RESTAPI::restAPI_GetP25DumpTSBK(const HTTPRequest& request, HTTPReply& repl
/// <param name="request"></param> /// <param name="request"></param>
/// <param name="reply"></param> /// <param name="reply"></param>
/// <param name="match"></param> /// <param name="match"></param>
void RESTAPI::restAPI_PutP25RID(const HTTPRequest& request, HTTPReply& reply, const RequestMatch& match) void RESTAPI::restAPI_PutP25RID(const HTTPPayload& request, HTTPPayload& reply, const RequestMatch& match)
{ {
if (!validateAuth(request, reply)) { if (!validateAuth(request, reply)) {
return; return;
@ -1414,7 +1414,7 @@ void RESTAPI::restAPI_PutP25RID(const HTTPRequest& request, HTTPReply& reply, co
return; return;
} }
errorReply(reply, "OK", HTTPReply::OK); errorPayload(reply, "OK", HTTPPayload::OK);
} }
/// <summary> /// <summary>
@ -1423,18 +1423,18 @@ void RESTAPI::restAPI_PutP25RID(const HTTPRequest& request, HTTPReply& reply, co
/// <param name="request"></param> /// <param name="request"></param>
/// <param name="reply"></param> /// <param name="reply"></param>
/// <param name="match"></param> /// <param name="match"></param>
void RESTAPI::restAPI_GetP25CCEnable(const HTTPRequest& request, HTTPReply& reply, const RequestMatch& match) void RESTAPI::restAPI_GetP25CCEnable(const HTTPPayload& request, HTTPPayload& reply, const RequestMatch& match)
{ {
if (!validateAuth(request, reply)) { if (!validateAuth(request, reply)) {
return; return;
} }
if (match.size() < 2) { if (match.size() < 2) {
errorReply(reply, "invalid API call arguments"); errorPayload(reply, "invalid API call arguments");
return; return;
} }
errorReply(reply, "OK", HTTPReply::OK); errorPayload(reply, "OK", HTTPPayload::OK);
} }
/// <summary> /// <summary>
@ -1443,18 +1443,18 @@ void RESTAPI::restAPI_GetP25CCEnable(const HTTPRequest& request, HTTPReply& repl
/// <param name="request"></param> /// <param name="request"></param>
/// <param name="reply"></param> /// <param name="reply"></param>
/// <param name="match"></param> /// <param name="match"></param>
void RESTAPI::restAPI_GetP25CCBroadcast(const HTTPRequest& request, HTTPReply& reply, const RequestMatch& match) void RESTAPI::restAPI_GetP25CCBroadcast(const HTTPPayload& request, HTTPPayload& reply, const RequestMatch& match)
{ {
if (!validateAuth(request, reply)) { if (!validateAuth(request, reply)) {
return; return;
} }
if (match.size() < 2) { if (match.size() < 2) {
errorReply(reply, "invalid API call arguments"); errorPayload(reply, "invalid API call arguments");
return; return;
} }
errorReply(reply, "OK", HTTPReply::OK); errorPayload(reply, "OK", HTTPPayload::OK);
} }
/* /*
@ -1467,7 +1467,7 @@ void RESTAPI::restAPI_GetP25CCBroadcast(const HTTPRequest& request, HTTPReply& r
/// <param name="request"></param> /// <param name="request"></param>
/// <param name="reply"></param> /// <param name="reply"></param>
/// <param name="match"></param> /// <param name="match"></param>
void RESTAPI::restAPI_GetNXDNDebug(const HTTPRequest& request, HTTPReply& reply, const RequestMatch& match) void RESTAPI::restAPI_GetNXDNDebug(const HTTPPayload& request, HTTPPayload& reply, const RequestMatch& match)
{ {
if (!validateAuth(request, reply)) { if (!validateAuth(request, reply)) {
return; return;
@ -1476,7 +1476,7 @@ void RESTAPI::restAPI_GetNXDNDebug(const HTTPRequest& request, HTTPReply& reply,
json::object response = json::object(); json::object response = json::object();
setResponseDefaultStatus(response); setResponseDefaultStatus(response);
#if defined(ENABLE_NXDN) #if defined(ENABLE_NXDN)
errorReply(reply, "OK", HTTPReply::OK); errorPayload(reply, "OK", HTTPPayload::OK);
if (m_dmr != nullptr) { if (m_dmr != nullptr) {
if (match.size() <= 1) { if (match.size() <= 1) {
bool debug = m_nxdn->getDebug(); bool debug = m_nxdn->getDebug();
@ -1485,7 +1485,7 @@ void RESTAPI::restAPI_GetNXDNDebug(const HTTPRequest& request, HTTPReply& reply,
response["debug"].set<bool>(debug); response["debug"].set<bool>(debug);
response["verbose"].set<bool>(verbose); response["verbose"].set<bool>(verbose);
reply.reply(response); reply.payload(response);
return; return;
} }
else { else {
@ -1497,11 +1497,11 @@ void RESTAPI::restAPI_GetNXDNDebug(const HTTPRequest& request, HTTPReply& reply,
} }
} }
else { else {
errorReply(reply, "NXDN mode is not enabled", HTTPReply::SERVICE_UNAVAILABLE); errorPayload(reply, "NXDN mode is not enabled", HTTPPayload::SERVICE_UNAVAILABLE);
return; return;
} }
#else #else
errorReply(reply, "NXDN operations are unavailable", HTTPReply::SERVICE_UNAVAILABLE); errorPayload(reply, "NXDN operations are unavailable", HTTPPayload::SERVICE_UNAVAILABLE);
#endif // defined(ENABLE_NXDN) #endif // defined(ENABLE_NXDN)
} }
@ -1511,7 +1511,7 @@ void RESTAPI::restAPI_GetNXDNDebug(const HTTPRequest& request, HTTPReply& reply,
/// <param name="request"></param> /// <param name="request"></param>
/// <param name="reply"></param> /// <param name="reply"></param>
/// <param name="match"></param> /// <param name="match"></param>
void RESTAPI::restAPI_GetNXDNDumpRCCH(const HTTPRequest& request, HTTPReply& reply, const RequestMatch& match) void RESTAPI::restAPI_GetNXDNDumpRCCH(const HTTPPayload& request, HTTPPayload& reply, const RequestMatch& match)
{ {
if (!validateAuth(request, reply)) { if (!validateAuth(request, reply)) {
return; return;
@ -1520,14 +1520,14 @@ void RESTAPI::restAPI_GetNXDNDumpRCCH(const HTTPRequest& request, HTTPReply& rep
json::object response = json::object(); json::object response = json::object();
setResponseDefaultStatus(response); setResponseDefaultStatus(response);
#if defined(ENABLE_NXDN) #if defined(ENABLE_NXDN)
errorReply(reply, "OK", HTTPReply::OK); errorPayload(reply, "OK", HTTPPayload::OK);
if (m_p25 != nullptr) { if (m_p25 != nullptr) {
if (match.size() <= 1) { if (match.size() <= 1) {
bool rcchDump = m_nxdn->getRCCHVerbose(); bool rcchDump = m_nxdn->getRCCHVerbose();
response["verbose"].set<bool>(rcchDump); response["verbose"].set<bool>(rcchDump);
reply.reply(response); reply.payload(response);
return; return;
} }
else { else {
@ -1538,10 +1538,10 @@ void RESTAPI::restAPI_GetNXDNDumpRCCH(const HTTPRequest& request, HTTPReply& rep
} }
} }
else { else {
errorReply(reply, "NXDN mode is not enabled", HTTPReply::SERVICE_UNAVAILABLE); errorPayload(reply, "NXDN mode is not enabled", HTTPPayload::SERVICE_UNAVAILABLE);
return; return;
} }
#else #else
errorReply(reply, "NXDN operations are unavailable", HTTPReply::SERVICE_UNAVAILABLE); errorPayload(reply, "NXDN operations are unavailable", HTTPPayload::SERVICE_UNAVAILABLE);
#endif // defined(ENABLE_NXDN) #endif // defined(ENABLE_NXDN)
} }

@ -118,9 +118,8 @@ public:
void close(); void close();
private: private:
typedef network::rest::RequestDispatcher<network::rest::http::HTTPRequest, network::rest::http::HTTPReply> RESTDispatcherType; typedef network::rest::RequestDispatcher<network::rest::http::HTTPPayload, network::rest::http::HTTPPayload> RESTDispatcherType;
typedef network::rest::http::HTTPRequest HTTPRequest; typedef network::rest::http::HTTPPayload HTTPPayload;
typedef network::rest::http::HTTPReply HTTPReply;
RESTDispatcherType m_dispatcher; RESTDispatcherType m_dispatcher;
network::rest::http::HTTPServer<RESTDispatcherType> m_restServer; network::rest::http::HTTPServer<RESTDispatcherType> m_restServer;
@ -152,79 +151,79 @@ private:
/// <summary></summary> /// <summary></summary>
void invalidateHostToken(const std::string host); void invalidateHostToken(const std::string host);
/// <summary></summary> /// <summary></summary>
bool validateAuth(const HTTPRequest& request, HTTPReply& reply); bool validateAuth(const HTTPPayload& request, HTTPPayload& reply);
/// <summary></summary> /// <summary></summary>
void restAPI_PutAuth(const HTTPRequest& request, HTTPReply& reply, const network::rest::RequestMatch& match); void restAPI_PutAuth(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match);
/// <summary></summary> /// <summary></summary>
void restAPI_GetVersion(const HTTPRequest& request, HTTPReply& reply, const network::rest::RequestMatch& match); void restAPI_GetVersion(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match);
/// <summary></summary> /// <summary></summary>
void restAPI_GetStatus(const HTTPRequest& request, HTTPReply& reply, const network::rest::RequestMatch& match); void restAPI_GetStatus(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match);
/// <summary></summary> /// <summary></summary>
void restAPI_GetVoiceCh(const HTTPRequest& request, HTTPReply& reply, const network::rest::RequestMatch& match); void restAPI_GetVoiceCh(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match);
/// <summary></summary> /// <summary></summary>
void restAPI_PutModemMode(const HTTPRequest& request, HTTPReply& reply, const network::rest::RequestMatch& match); void restAPI_PutModemMode(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match);
/// <summary></summary> /// <summary></summary>
void restAPI_PutModemKill(const HTTPRequest& request, HTTPReply& reply, const network::rest::RequestMatch& match); void restAPI_PutModemKill(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match);
/// <summary></summary> /// <summary></summary>
void restAPI_PutPermitTG(const HTTPRequest& request, HTTPReply& reply, const network::rest::RequestMatch& match); void restAPI_PutPermitTG(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match);
/// <summary></summary> /// <summary></summary>
void restAPI_PutGrantTG(const HTTPRequest& request, HTTPReply& reply, const network::rest::RequestMatch& match); void restAPI_PutGrantTG(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match);
/// <summary></summary> /// <summary></summary>
void restAPI_GetReleaseGrants(const HTTPRequest& request, HTTPReply& reply, const network::rest::RequestMatch& match); void restAPI_GetReleaseGrants(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match);
/// <summary></summary> /// <summary></summary>
void restAPI_GetReleaseAffs(const HTTPRequest& request, HTTPReply& reply, const network::rest::RequestMatch& match); void restAPI_GetReleaseAffs(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match);
/// <summary></summary> /// <summary></summary>
void restAPI_GetRIDWhitelist(const HTTPRequest& request, HTTPReply& reply, const network::rest::RequestMatch& match); void restAPI_GetRIDWhitelist(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match);
/// <summary></summary> /// <summary></summary>
void restAPI_GetRIDBlacklist(const HTTPRequest& request, HTTPReply& reply, const network::rest::RequestMatch& match); void restAPI_GetRIDBlacklist(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match);
/* /*
** Digital Mobile Radio ** Digital Mobile Radio
*/ */
/// <summary></summary> /// <summary></summary>
void restAPI_GetDMRBeacon(const HTTPRequest& request, HTTPReply& reply, const network::rest::RequestMatch& match); void restAPI_GetDMRBeacon(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match);
/// <summary></summary> /// <summary></summary>
void restAPI_GetDMRDebug(const HTTPRequest& request, HTTPReply& reply, const network::rest::RequestMatch& match); void restAPI_GetDMRDebug(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match);
/// <summary></summary> /// <summary></summary>
void restAPI_GetDMRDumpCSBK(const HTTPRequest& request, HTTPReply& reply, const network::rest::RequestMatch& match); void restAPI_GetDMRDumpCSBK(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match);
/// <summary></summary> /// <summary></summary>
void restAPI_PutDMRRID(const HTTPRequest& request, HTTPReply& reply, const network::rest::RequestMatch& match); void restAPI_PutDMRRID(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match);
/// <summary></summary> /// <summary></summary>
void restAPI_GetDMRCCEnable(const HTTPRequest& request, HTTPReply& reply, const network::rest::RequestMatch& match); void restAPI_GetDMRCCEnable(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match);
/// <summary></summary> /// <summary></summary>
void restAPI_GetDMRCCBroadcast(const HTTPRequest& request, HTTPReply& reply, const network::rest::RequestMatch& match); void restAPI_GetDMRCCBroadcast(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match);
/* /*
** Project 25 ** Project 25
*/ */
/// <summary></summary> /// <summary></summary>
void restAPI_GetP25CC(const HTTPRequest& request, HTTPReply& reply, const network::rest::RequestMatch& match); void restAPI_GetP25CC(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match);
/// <summary></summary> /// <summary></summary>
void restAPI_GetP25Debug(const HTTPRequest& request, HTTPReply& reply, const network::rest::RequestMatch& match); void restAPI_GetP25Debug(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match);
/// <summary></summary> /// <summary></summary>
void restAPI_GetP25DumpTSBK(const HTTPRequest& request, HTTPReply& reply, const network::rest::RequestMatch& match); void restAPI_GetP25DumpTSBK(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match);
/// <summary></summary> /// <summary></summary>
void restAPI_PutP25RID(const HTTPRequest& request, HTTPReply& reply, const network::rest::RequestMatch& match); void restAPI_PutP25RID(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match);
/// <summary></summary> /// <summary></summary>
void restAPI_GetP25CCEnable(const HTTPRequest& request, HTTPReply& reply, const network::rest::RequestMatch& match); void restAPI_GetP25CCEnable(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match);
/// <summary></summary> /// <summary></summary>
void restAPI_GetP25CCBroadcast(const HTTPRequest& request, HTTPReply& reply, const network::rest::RequestMatch& match); void restAPI_GetP25CCBroadcast(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match);
/* /*
** Next Generation Digital Narrowband ** Next Generation Digital Narrowband
*/ */
/// <summary></summary> /// <summary></summary>
void restAPI_GetNXDNDebug(const HTTPRequest& request, HTTPReply& reply, const network::rest::RequestMatch& match); void restAPI_GetNXDNDebug(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match);
/// <summary></summary> /// <summary></summary>
void restAPI_GetNXDNDumpRCCH(const HTTPRequest& request, HTTPReply& reply, const network::rest::RequestMatch& match); void restAPI_GetNXDNDumpRCCH(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match);
}; };
#endif // __REST_API_H__ #endif // __REST_API_H__

@ -27,8 +27,7 @@
#define __REST__DISPATCHER_H__ #define __REST__DISPATCHER_H__
#include "Defines.h" #include "Defines.h"
#include "network/rest/http/HTTPRequest.h" #include "network/rest/http/HTTPPayload.h"
#include "network/rest/http/HTTPReply.h"
#include "Log.h" #include "Log.h"
#include <functional> #include <functional>
@ -68,27 +67,27 @@ namespace network
/// <summary></summary> /// <summary></summary>
RequestMatcher<Request, Reply>& get(RequestHandlerType handler) { RequestMatcher<Request, Reply>& get(RequestHandlerType handler) {
m_handlers["GET"] = handler; m_handlers[HTTP_GET] = handler;
return *this; return *this;
} }
/// <summary></summary> /// <summary></summary>
RequestMatcher<Request, Reply>& post(RequestHandlerType handler) { RequestMatcher<Request, Reply>& post(RequestHandlerType handler) {
m_handlers["POST"] = handler; m_handlers[HTTP_POST] = handler;
return *this; return *this;
} }
/// <summary></summary> /// <summary></summary>
RequestMatcher<Request, Reply>& put(RequestHandlerType handler) { RequestMatcher<Request, Reply>& put(RequestHandlerType handler) {
m_handlers["PUT"] = handler; m_handlers[HTTP_PUT] = handler;
return *this; return *this;
} }
/// <summary></summary> /// <summary></summary>
RequestMatcher<Request, Reply>& del(RequestHandlerType handler) { RequestMatcher<Request, Reply>& del(RequestHandlerType handler) {
m_handlers["DELETE"] = handler; m_handlers[HTTP_DELETE] = handler;
return *this; return *this;
} }
/// <summary></summary> /// <summary></summary>
RequestMatcher<Request, Reply>& options(RequestHandlerType handler) { RequestMatcher<Request, Reply>& options(RequestHandlerType handler) {
m_handlers["OPTIONS"] = handler; m_handlers[HTTP_OPTIONS] = handler;
return *this; return *this;
} }
@ -116,7 +115,7 @@ namespace network
// This class implements RESTful web request dispatching. // This class implements RESTful web request dispatching.
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
template<typename Request = http::HTTPRequest, typename Reply = http::HTTPReply> template<typename Request = http::HTTPPayload, typename Reply = http::HTTPPayload>
class RequestDispatcher { class RequestDispatcher {
typedef RequestMatcher<Request, Reply> MatcherType; typedef RequestMatcher<Request, Reply> MatcherType;
public: public:
@ -174,7 +173,7 @@ namespace network
} }
::LogError(LOG_REST, "unknown endpoint, uri = %s", request.uri.c_str()); ::LogError(LOG_REST, "unknown endpoint, uri = %s", request.uri.c_str());
reply = http::HTTPReply::stockReply(http::HTTPReply::BAD_REQUEST, "application/json"); reply = http::HTTPPayload::statusPayload(http::HTTPPayload::BAD_REQUEST, "application/json");
} }
private: private:
@ -186,7 +185,7 @@ namespace network
bool m_debug; bool m_debug;
}; };
typedef RequestDispatcher<http::HTTPRequest, http::HTTPReply> DefaultRequestDispatcher; typedef RequestDispatcher<http::HTTPPayload, http::HTTPPayload> DefaultRequestDispatcher;
} // namespace rest } // namespace rest
} // namespace network } // namespace network

@ -38,9 +38,8 @@
#define __REST_HTTP__CONNECTION_H__ #define __REST_HTTP__CONNECTION_H__
#include "Defines.h" #include "Defines.h"
#include "network/rest/http/HTTPRequest.h" #include "network/rest/http/HTTPLexer.h"
#include "network/rest/http/HTTPRequestLexer.h" #include "network/rest/http/HTTPPayload.h"
#include "network/rest/http/HTTPReply.h"
#include <array> #include <array>
#include <memory> #include <memory>
@ -75,11 +74,13 @@ namespace network
public: public:
/// <summary>Initializes a new instance of the Connection class.</summary> /// <summary>Initializes a new instance of the Connection class.</summary>
explicit Connection(asio::ip::tcp::socket socket, ConnectionManagerType& manager, RequestHandlerType& handler, explicit Connection(asio::ip::tcp::socket socket, ConnectionManagerType& manager, RequestHandlerType& handler,
bool persistent = false) : bool persistent = false, bool client = false) :
m_socket(std::move(socket)), m_socket(std::move(socket)),
m_connectionManager(manager), m_connectionManager(manager),
m_requestHandler(handler), m_requestHandler(handler),
m_persistent(persistent) m_lexer(HTTPLexer(client)),
m_persistent(persistent),
m_client(client)
{ {
/* stub */ /* stub */
} }
@ -103,7 +104,7 @@ namespace network
m_socket.async_read_some(asio::buffer(m_buffer), [=](asio::error_code ec, std::size_t bytes_transferred) { m_socket.async_read_some(asio::buffer(m_buffer), [=](asio::error_code ec, std::size_t bytes_transferred) {
if (!ec) { if (!ec) {
HTTPRequestLexer::ResultType result; HTTPLexer::ResultType result;
char* content; char* content;
std::tie(result, content) = m_lexer.parse(m_request, m_buffer.data(), m_buffer.data() + bytes_transferred); std::tie(result, content) = m_lexer.parse(m_request, m_buffer.data(), m_buffer.data() + bytes_transferred);
@ -114,27 +115,41 @@ namespace network
m_request.content = std::string(content, length); m_request.content = std::string(content, length);
} }
if (result == HTTPRequestLexer::GOOD) { if (m_client) {
m_requestHandler.handleRequest(m_request, m_reply);
}
else {
if (result == HTTPLexer::GOOD) {
m_requestHandler.handleRequest(m_request, m_reply); m_requestHandler.handleRequest(m_request, m_reply);
write(); write();
} }
else if (result == HTTPRequestLexer::BAD) { else if (result == HTTPLexer::BAD) {
m_reply = HTTPReply::stockReply(HTTPReply::BAD_REQUEST); m_reply = HTTPPayload::statusPayload(HTTPPayload::BAD_REQUEST);
write(); write();
} }
else { else {
read(); read();
} }
} }
}
else if (ec != asio::error::operation_aborted) { else if (ec != asio::error::operation_aborted) {
if (m_client) {
m_socket.close();
}
else {
m_connectionManager.stop(this->shared_from_this()); m_connectionManager.stop(this->shared_from_this());
} }
}
}); });
} }
/// <summary>Perform an asynchronous write operation.</summary> /// <summary>Perform an asynchronous write operation.</summary>
void write() void write()
{ {
if (m_client) {
return;
}
if (!m_persistent) { if (!m_persistent) {
auto self(this->shared_from_this()); auto self(this->shared_from_this());
} else { } else {
@ -145,9 +160,9 @@ namespace network
if (m_persistent) { if (m_persistent) {
m_lexer.reset(); m_lexer.reset();
m_reply.headers = HTTPHeaders(); m_reply.headers = HTTPHeaders();
m_reply.status = HTTPReply::OK; m_reply.status = HTTPPayload::OK;
m_reply.content = ""; m_reply.content = "";
m_request = HTTPRequest(); m_request = HTTPPayload();
read(); read();
} }
else else
@ -166,13 +181,18 @@ namespace network
} }
asio::ip::tcp::socket m_socket; asio::ip::tcp::socket m_socket;
ConnectionManagerType& m_connectionManager; ConnectionManagerType& m_connectionManager;
RequestHandlerType& m_requestHandler; RequestHandlerType& m_requestHandler;
std::array<char, 8192> m_buffer; std::array<char, 8192> m_buffer;
HTTPRequest m_request;
HTTPRequestLexer m_lexer; HTTPPayload m_request;
HTTPReply m_reply; HTTPLexer m_lexer;
HTTPPayload m_reply;
bool m_persistent; bool m_persistent;
bool m_client;
}; };
} // namespace http } // namespace http
} // namespace rest } // namespace rest

@ -0,0 +1,180 @@
/**
* 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
*
*/
/*
* 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_CLIENT_H__)
#define __REST_HTTP__HTTP_CLIENT_H__
#include "Defines.h"
#include "network/rest/http/Connection.h"
#include "network/rest/http/ConnectionManager.h"
#include "network/rest/http/HTTPRequestHandler.h"
#include "Thread.h"
#include <asio.hpp>
#include <thread>
#include <string>
#include <signal.h>
#include <utility>
#include <memory>
#include <mutex>
namespace network
{
namespace rest
{
namespace http
{
// ---------------------------------------------------------------------------
// Class Declaration
// This class implements top-level routines of the HTTP client.
// ---------------------------------------------------------------------------
template<typename RequestHandlerType, template<class> class ConnectionImpl = Connection>
class HTTPClient : private Thread {
public:
/// <summary>Initializes a new instance of the HTTPClient class.</summary>
explicit HTTPClient(const std::string& address, uint16_t port) :
m_address(address),
m_port(port),
m_connection(nullptr),
m_ioContext(),
m_connectionManager(),
m_socket(m_ioContext),
m_requestHandler()
{
/* stub */
}
/// <summary>Initializes a copy instance of the HTTPClient class.</summary>
HTTPClient(const HTTPClient&) = delete;
/// <summary></summary>
HTTPClient& operator=(const HTTPClient&) = delete;
/// <summary>Helper to set the HTTP request handlers.</summary>
template<typename Handler>
void setHandler(Handler&& handler)
{
m_requestHandler = RequestHandlerType(std::forward<Handler>(handler));
}
/// <summary>Send HTTP request to HTTP server.</summary>
void request(HTTPPayload& request)
{
asio::post(m_ioContext, [this, request]() {
std::lock_guard<std::mutex> guard(m_lock);
{
write(request);
}
});
}
/// <summary>Opens connection to the network.</summary>
bool open()
{
return run();
}
/// <summary>Closes connection to the network.</summary>
void close()
{
if (m_connection != nullptr) {
m_connection.stop();
}
wait();
}
private:
/// <summary></summary>
virtual void entry()
{
asio::ip::tcp::resolver resolver(m_ioContext);
auto endpoints = resolver.resolve(m_address, std::to_string(m_port));
connect(endpoints);
// the entry() call will block until all asynchronous operations
// have finished
m_ioContext.run();
}
/// <summary>Perform an asynchronous connect operation.</summary>
void connect(asio::ip::basic_resolver_results<asio::ip::tcp>& endpoints)
{
asio::async_connect(m_socket, endpoints, [this](asio::error_code ec, asio::ip::tcp::endpoint) {
if (!ec) {
m_connection = std::make_shared<ConnectionType>(std::move(m_socket), m_connectionManager, m_requestHandler, false, true);
m_connection->start();
}
});
}
/// <summary>Perform an asynchronous write operation.</summary>
void write(HTTPPayload& request)
{
asio::async_write(m_socket, request.toBuffers(), [=](asio::error_code ec, std::size_t) {
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_socket.close();
}
});
}
std::string m_address;
uint16_t m_port;
typedef ConnectionImpl<RequestHandlerType> ConnectionType;
typedef std::shared_ptr<ConnectionType> ConnectionTypePtr;
ConnectionTypePtr m_connection;
asio::io_context m_ioContext;
ConnectionManager<ConnectionTypePtr> m_connectionManager;
asio::ip::tcp::socket m_socket;
RequestHandlerType m_requestHandler;
std::mutex m_lock;
};
} // namespace http
} // namespace rest
} // namespace network
#endif // __REST_HTTP__HTTP_CLIENT_H__

@ -54,7 +54,7 @@ namespace network
// Class Prototypes // Class Prototypes
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
struct HTTPReply; struct HTTPPayload;
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Structure Declaration // Structure Declaration
@ -122,7 +122,7 @@ namespace network
} }
private: private:
friend struct HTTPReply; friend struct HTTPPayload;
std::vector<Header> m_headers; std::vector<Header> m_headers;
}; };
} // namespace http } // namespace http

@ -35,8 +35,8 @@
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/ */
#include "Defines.h" #include "Defines.h"
#include "network/rest/http/HTTPRequestLexer.h" #include "network/rest/http/HTTPLexer.h"
#include "network/rest/http/HTTPRequest.h" #include "network/rest/http/HTTPPayload.h"
#include "Log.h" #include "Log.h"
using namespace network::rest::http; using namespace network::rest::http;
@ -48,20 +48,27 @@ using namespace network::rest::http;
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
/// <summary> /// <summary>
/// Initializes a new instance of the HTTPRequestLexer class. /// Initializes a new instance of the HTTPLexer class.
/// </summary> /// </summary>
/// <param name="clientLexer"></param>
HTTPRequestLexer::HTTPRequestLexer() : HTTPLexer::HTTPLexer(bool clientLexer) :
m_headers(), m_headers(),
m_clientLexer(clientLexer),
m_state(METHOD_START) m_state(METHOD_START)
{ {
/* stub */ if (m_clientLexer) {
m_state = HTTP_VERSION_H;
}
} }
/// <summary>Reset to initial parser state.</summary> /// <summary>Reset to initial parser state.</summary>
void HTTPRequestLexer::reset() void HTTPLexer::reset()
{ {
m_state = METHOD_START; m_state = METHOD_START;
if (m_clientLexer) {
m_state = HTTP_VERSION_H;
}
m_headers = std::vector<LexedHeader>(); m_headers = std::vector<LexedHeader>();
} }
@ -75,7 +82,7 @@ void HTTPRequestLexer::reset()
/// <param name="req"></param> /// <param name="req"></param>
/// <param name="input"></param> /// <param name="input"></param>
/// <returns></returns> /// <returns></returns>
HTTPRequestLexer::ResultType HTTPRequestLexer::consume(HTTPRequest& req, char input) HTTPLexer::ResultType HTTPLexer::consume(HTTPPayload& req, char input)
{ {
switch (m_state) switch (m_state)
{ {
@ -123,6 +130,7 @@ HTTPRequestLexer::ResultType HTTPRequestLexer::consume(HTTPRequest& req, char in
/* /*
** HTTP/1.0 ** HTTP/1.0
** HTTP/1.0 200 OK
*/ */
case HTTP_VERSION_H: case HTTP_VERSION_H:
if (input == 'H') { if (input == 'H') {
@ -197,19 +205,86 @@ HTTPRequestLexer::ResultType HTTPRequestLexer::consume(HTTPRequest& req, char in
return BAD; return BAD;
} }
case HTTP_VERSION_MINOR: case HTTP_VERSION_MINOR:
if (input == '\r') if (input == '\r') {
{
m_state = EXPECTING_NEWLINE_1; m_state = EXPECTING_NEWLINE_1;
if (m_clientLexer) {
return BAD;
}
else {
return INDETERMINATE; return INDETERMINATE;
} }
else if (isDigit(input)) }
{ else if (input == ' ') {
if (m_clientLexer) {
m_state = HTTP_STATUS_1;
return INDETERMINATE;
}
else {
return BAD;
}
}
else if (isDigit(input)) {
req.httpVersionMinor = req.httpVersionMinor * 10 + input - '0'; req.httpVersionMinor = req.httpVersionMinor * 10 + input - '0';
return INDETERMINATE; return INDETERMINATE;
} }
else { else {
return BAD; return BAD;
} }
case HTTP_STATUS_1:
if (isDigit(input)) {
m_state = HTTP_STATUS_2;
m_status = m_status * 10 + input - '0';
return INDETERMINATE;
}
else {
return BAD;
}
case HTTP_STATUS_2:
if (isDigit(input)) {
m_state = HTTP_STATUS_3;
m_status = m_status * 10 + input - '0';
return INDETERMINATE;
}
else {
return BAD;
}
case HTTP_STATUS_3:
if (isDigit(input)) {
m_state = HTTP_STATUS_END;
m_status = m_status * 10 + input - '0';
req.status = (HTTPPayload::StatusType)m_status;
return INDETERMINATE;
}
else {
return BAD;
}
case HTTP_STATUS_END:
if (input == ' ') {
m_state = HTTP_STATUS_MESSAGE;
return INDETERMINATE;
}
else {
return BAD;
}
case HTTP_STATUS_MESSAGE_START:
if (!isChar(input) || isControl(input) || isSpecial(input)) {
return BAD;
}
else {
m_state = HTTP_STATUS_MESSAGE;
return INDETERMINATE;
}
case HTTP_STATUS_MESSAGE:
if (input == '\r') {
m_state = EXPECTING_NEWLINE_1;
return INDETERMINATE;
}
else if (!isChar(input) || isControl(input) || isSpecial(input)) {
return BAD;
}
else {
return INDETERMINATE;
}
case EXPECTING_NEWLINE_1: case EXPECTING_NEWLINE_1:
if (input == '\n') { if (input == '\n') {
@ -275,8 +350,7 @@ HTTPRequestLexer::ResultType HTTPRequestLexer::consume(HTTPRequest& req, char in
} }
case SPACE_BEFORE_HEADER_VALUE: case SPACE_BEFORE_HEADER_VALUE:
if (input == ' ') if (input == ' ') {
{
m_state = HEADER_VALUE; m_state = HEADER_VALUE;
return INDETERMINATE; return INDETERMINATE;
} }
@ -309,7 +383,7 @@ HTTPRequestLexer::ResultType HTTPRequestLexer::consume(HTTPRequest& req, char in
case EXPECTING_NEWLINE_3: case EXPECTING_NEWLINE_3:
if (input == '\n') { if (input == '\n') {
for (auto header : m_headers) { for (auto header : m_headers) {
//::LogDebug(LOG_REST, "HTTPRequestLexer::consume(), header = %s, value = %s", header.name.c_str(), header.value.c_str()); //::LogDebug(LOG_REST, "HTTPLexer::consume(), header = %s, value = %s", header.name.c_str(), header.value.c_str());
req.headers.add(header.name, header.value); req.headers.add(header.name, header.value);
} }
@ -328,7 +402,7 @@ HTTPRequestLexer::ResultType HTTPRequestLexer::consume(HTTPRequest& req, char in
/// </summary> /// </summary>
/// <param name="c"></param> /// <param name="c"></param>
/// <returns></returns> /// <returns></returns>
bool HTTPRequestLexer::isChar(int c) bool HTTPLexer::isChar(int c)
{ {
return c >= 0 && c <= 127; return c >= 0 && c <= 127;
} }
@ -338,7 +412,7 @@ bool HTTPRequestLexer::isChar(int c)
/// </summary> /// </summary>
/// <param name="c"></param> /// <param name="c"></param>
/// <returns></returns> /// <returns></returns>
bool HTTPRequestLexer::isControl(int c) bool HTTPLexer::isControl(int c)
{ {
return (c >= 0 && c <= 31) || (c == 127); return (c >= 0 && c <= 31) || (c == 127);
} }
@ -348,7 +422,7 @@ bool HTTPRequestLexer::isControl(int c)
/// </summary> /// </summary>
/// <param name="c"></param> /// <param name="c"></param>
/// <returns></returns> /// <returns></returns>
bool HTTPRequestLexer::isSpecial(int c) bool HTTPLexer::isSpecial(int c)
{ {
switch (c) switch (c)
{ {
@ -367,7 +441,7 @@ bool HTTPRequestLexer::isSpecial(int c)
/// </summary> /// </summary>
/// <param name="c"></param> /// <param name="c"></param>
/// <returns></returns> /// <returns></returns>
bool HTTPRequestLexer::isDigit(int c) bool HTTPLexer::isDigit(int c)
{ {
return c >= '0' && c <= '9'; return c >= '0' && c <= '9';
} }

@ -34,8 +34,10 @@
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/ */
#if !defined(__REST_HTTP__HTTP_REQUEST_PARSER_H__) #if !defined(__REST_HTTP__HTTP_LEXER_H__)
#define __REST_HTTP__HTTP_REQUEST_PARSER_H__ #define __REST_HTTP__HTTP_LEXER_H__
#include "Defines.h"
#include <tuple> #include <tuple>
#include <vector> #include <vector>
@ -51,20 +53,20 @@ namespace network
// Class Prototypes // Class Prototypes
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
struct HTTPRequest; struct HTTPPayload;
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Class Declaration // Class Declaration
// This class implements the lexer for incoming requests. // This class implements the lexer for incoming payloads.
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
class HTTPRequestLexer class HTTPLexer
{ {
public: public:
enum ResultType { GOOD, BAD, INDETERMINATE }; enum ResultType { GOOD, BAD, INDETERMINATE };
/// <summary>Initializes a new instance of the HTTPRequestLexer class.</summary> /// <summary>Initializes a new instance of the HTTPLexer class.</summary>
HTTPRequestLexer(); HTTPLexer(bool clientLexer);
/// <summary>Reset to initial parser state.</summary> /// <summary>Reset to initial parser state.</summary>
void reset(); void reset();
@ -74,10 +76,10 @@ namespace network
/// required. The InputIterator return value indicates how much of the input /// required. The InputIterator return value indicates how much of the input
/// has been consumed.</summary> /// has been consumed.</summary>
template <typename InputIterator> template <typename InputIterator>
std::tuple<ResultType, InputIterator> parse(HTTPRequest& req, InputIterator begin, InputIterator end) std::tuple<ResultType, InputIterator> parse(HTTPPayload& payload, InputIterator begin, InputIterator end)
{ {
while (begin != end) { while (begin != end) {
ResultType result = consume(req, *begin++); ResultType result = consume(payload, *begin++);
if (result == GOOD || result == BAD) if (result == GOOD || result == BAD)
return std::make_tuple(result, begin); return std::make_tuple(result, begin);
} }
@ -86,7 +88,7 @@ namespace network
private: private:
/// <summary>Handle the next character of input.</summary> /// <summary>Handle the next character of input.</summary>
ResultType consume(HTTPRequest& req, char input); ResultType consume(HTTPPayload& payload, char input);
/// <summary>Check if a byte is an HTTP character.</summary> /// <summary>Check if a byte is an HTTP character.</summary>
static bool isChar(int c); static bool isChar(int c);
@ -107,6 +109,8 @@ namespace network
}; };
std::vector<LexedHeader> m_headers; std::vector<LexedHeader> m_headers;
uint16_t m_status;
bool m_clientLexer = false;
enum state enum state
{ {
@ -122,6 +126,12 @@ namespace network
HTTP_VERSION_MAJOR, HTTP_VERSION_MAJOR,
HTTP_VERSION_MINOR_START, HTTP_VERSION_MINOR_START,
HTTP_VERSION_MINOR, HTTP_VERSION_MINOR,
HTTP_STATUS_1,
HTTP_STATUS_2,
HTTP_STATUS_3,
HTTP_STATUS_END,
HTTP_STATUS_MESSAGE_START,
HTTP_STATUS_MESSAGE,
EXPECTING_NEWLINE_1, EXPECTING_NEWLINE_1,
HEADER_LINE_START, HEADER_LINE_START,
HEADER_LWS, HEADER_LWS,
@ -136,4 +146,4 @@ namespace network
} // namespace rest } // namespace rest
} // namespace network } // namespace network
#endif // __REST_HTTP__HTTP_REQUEST_PARSER_H__ #endif // __REST_HTTP__HTTP_LEXER_H__

@ -35,7 +35,7 @@
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/ */
#include "Defines.h" #include "Defines.h"
#include "network/rest/http/HTTPReply.h" #include "network/rest/http/HTTPPayload.h"
#include "Log.h" #include "Log.h"
using namespace network::rest::http; using namespace network::rest::http;
@ -60,41 +60,41 @@ namespace status_strings {
const std::string bad_gateway = "HTTP/1.0 502 Bad Gateway\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"; const std::string service_unavailable = "HTTP/1.0 503 Service Unavailable\r\n";
asio::const_buffer toBuffer(HTTPReply::StatusType status) asio::const_buffer toBuffer(HTTPPayload::StatusType status)
{ {
switch (status) switch (status)
{ {
case HTTPReply::OK: case HTTPPayload::OK:
return asio::buffer(ok); return asio::buffer(ok);
case HTTPReply::CREATED: case HTTPPayload::CREATED:
return asio::buffer(created); return asio::buffer(created);
case HTTPReply::ACCEPTED: case HTTPPayload::ACCEPTED:
return asio::buffer(accepted); return asio::buffer(accepted);
case HTTPReply::NO_CONTENT: case HTTPPayload::NO_CONTENT:
return asio::buffer(no_content); return asio::buffer(no_content);
case HTTPReply::MULTIPLE_CHOICES: case HTTPPayload::MULTIPLE_CHOICES:
return asio::buffer(multiple_choices); return asio::buffer(multiple_choices);
case HTTPReply::MOVED_PERMANENTLY: case HTTPPayload::MOVED_PERMANENTLY:
return asio::buffer(moved_permanently); return asio::buffer(moved_permanently);
case HTTPReply::MOVED_TEMPORARILY: case HTTPPayload::MOVED_TEMPORARILY:
return asio::buffer(moved_temporarily); return asio::buffer(moved_temporarily);
case HTTPReply::NOT_MODIFIED: case HTTPPayload::NOT_MODIFIED:
return asio::buffer(not_modified); return asio::buffer(not_modified);
case HTTPReply::BAD_REQUEST: case HTTPPayload::BAD_REQUEST:
return asio::buffer(bad_request); return asio::buffer(bad_request);
case HTTPReply::UNAUTHORIZED: case HTTPPayload::UNAUTHORIZED:
return asio::buffer(unauthorized); return asio::buffer(unauthorized);
case HTTPReply::FORBIDDEN: case HTTPPayload::FORBIDDEN:
return asio::buffer(forbidden); return asio::buffer(forbidden);
case HTTPReply::NOT_FOUND: case HTTPPayload::NOT_FOUND:
return asio::buffer(not_found); return asio::buffer(not_found);
case HTTPReply::INTERNAL_SERVER_ERROR: case HTTPPayload::INTERNAL_SERVER_ERROR:
return asio::buffer(internal_server_error); return asio::buffer(internal_server_error);
case HTTPReply::NOT_IMPLEMENTED: case HTTPPayload::NOT_IMPLEMENTED:
return asio::buffer(not_implemented); return asio::buffer(not_implemented);
case HTTPReply::BAD_GATEWAY: case HTTPPayload::BAD_GATEWAY:
return asio::buffer(bad_gateway); return asio::buffer(bad_gateway);
case HTTPReply::SERVICE_UNAVAILABLE: case HTTPPayload::SERVICE_UNAVAILABLE:
return asio::buffer(service_unavailable); return asio::buffer(service_unavailable);
default: default:
return asio::buffer(internal_server_error); return asio::buffer(internal_server_error);
@ -201,43 +201,43 @@ namespace stock_replies {
"</html>"; "</html>";
const char json_service_unavailable[] = "{status:503,message:\"service unavailable\"}"; const char json_service_unavailable[] = "{status:503,message:\"service unavailable\"}";
std::string to_string(HTTPReply::StatusType status, std::string contentType) std::string to_string(HTTPPayload::StatusType status, std::string contentType)
{ {
std::transform(contentType.begin(), contentType.end(), contentType.begin(), ::tolower); std::transform(contentType.begin(), contentType.end(), contentType.begin(), ::tolower);
if (contentType == "application/json") { if (contentType == "application/json") {
switch (status) switch (status)
{ {
case HTTPReply::OK: case HTTPPayload::OK:
return json_ok; return json_ok;
case HTTPReply::CREATED: case HTTPPayload::CREATED:
return json_created; return json_created;
case HTTPReply::ACCEPTED: case HTTPPayload::ACCEPTED:
return json_accepted; return json_accepted;
case HTTPReply::NO_CONTENT: case HTTPPayload::NO_CONTENT:
return json_no_content; return json_no_content;
case HTTPReply::MULTIPLE_CHOICES: case HTTPPayload::MULTIPLE_CHOICES:
return json_multiple_choices; return json_multiple_choices;
case HTTPReply::MOVED_PERMANENTLY: case HTTPPayload::MOVED_PERMANENTLY:
return json_moved_permanently; return json_moved_permanently;
case HTTPReply::MOVED_TEMPORARILY: case HTTPPayload::MOVED_TEMPORARILY:
return json_moved_temporarily; return json_moved_temporarily;
case HTTPReply::NOT_MODIFIED: case HTTPPayload::NOT_MODIFIED:
return json_not_modified; return json_not_modified;
case HTTPReply::BAD_REQUEST: case HTTPPayload::BAD_REQUEST:
return json_bad_request; return json_bad_request;
case HTTPReply::UNAUTHORIZED: case HTTPPayload::UNAUTHORIZED:
return json_unauthorized; return json_unauthorized;
case HTTPReply::FORBIDDEN: case HTTPPayload::FORBIDDEN:
return json_forbidden; return json_forbidden;
case HTTPReply::NOT_FOUND: case HTTPPayload::NOT_FOUND:
return json_not_found; return json_not_found;
case HTTPReply::INTERNAL_SERVER_ERROR: case HTTPPayload::INTERNAL_SERVER_ERROR:
return json_internal_server_error; return json_internal_server_error;
case HTTPReply::NOT_IMPLEMENTED: case HTTPPayload::NOT_IMPLEMENTED:
return json_not_implemented; return json_not_implemented;
case HTTPReply::BAD_GATEWAY: case HTTPPayload::BAD_GATEWAY:
return json_bad_gateway; return json_bad_gateway;
case HTTPReply::SERVICE_UNAVAILABLE: case HTTPPayload::SERVICE_UNAVAILABLE:
return json_service_unavailable; return json_service_unavailable;
default: default:
return json_internal_server_error; return json_internal_server_error;
@ -246,37 +246,37 @@ namespace stock_replies {
else { else {
switch (status) switch (status)
{ {
case HTTPReply::OK: case HTTPPayload::OK:
return ok; return ok;
case HTTPReply::CREATED: case HTTPPayload::CREATED:
return created; return created;
case HTTPReply::ACCEPTED: case HTTPPayload::ACCEPTED:
return accepted; return accepted;
case HTTPReply::NO_CONTENT: case HTTPPayload::NO_CONTENT:
return no_content; return no_content;
case HTTPReply::MULTIPLE_CHOICES: case HTTPPayload::MULTIPLE_CHOICES:
return multiple_choices; return multiple_choices;
case HTTPReply::MOVED_PERMANENTLY: case HTTPPayload::MOVED_PERMANENTLY:
return moved_permanently; return moved_permanently;
case HTTPReply::MOVED_TEMPORARILY: case HTTPPayload::MOVED_TEMPORARILY:
return moved_temporarily; return moved_temporarily;
case HTTPReply::NOT_MODIFIED: case HTTPPayload::NOT_MODIFIED:
return not_modified; return not_modified;
case HTTPReply::BAD_REQUEST: case HTTPPayload::BAD_REQUEST:
return bad_request; return bad_request;
case HTTPReply::UNAUTHORIZED: case HTTPPayload::UNAUTHORIZED:
return unauthorized; return unauthorized;
case HTTPReply::FORBIDDEN: case HTTPPayload::FORBIDDEN:
return forbidden; return forbidden;
case HTTPReply::NOT_FOUND: case HTTPPayload::NOT_FOUND:
return not_found; return not_found;
case HTTPReply::INTERNAL_SERVER_ERROR: case HTTPPayload::INTERNAL_SERVER_ERROR:
return internal_server_error; return internal_server_error;
case HTTPReply::NOT_IMPLEMENTED: case HTTPPayload::NOT_IMPLEMENTED:
return not_implemented; return not_implemented;
case HTTPReply::BAD_GATEWAY: case HTTPPayload::BAD_GATEWAY:
return bad_gateway; return bad_gateway;
case HTTPReply::SERVICE_UNAVAILABLE: case HTTPPayload::SERVICE_UNAVAILABLE:
return service_unavailable; return service_unavailable;
default: default:
return internal_server_error; return internal_server_error;
@ -294,14 +294,23 @@ namespace stock_replies {
/// underlying memory blocks, therefore the reply object must remain valid and /// underlying memory blocks, therefore the reply object must remain valid and
/// not be changed until the write operation has completed. /// not be changed until the write operation has completed.
/// </summary> /// </summary>
std::vector<asio::const_buffer> HTTPReply::toBuffers() std::vector<asio::const_buffer> HTTPPayload::toBuffers()
{ {
std::vector<asio::const_buffer> buffers; std::vector<asio::const_buffer> buffers;
if (isClientPayload) {
std::stringstream ss;
ss << ::strtoupper(method) << " " << uri << " " << HTTP_DEFAULT_VERSION;
std::string request = ss.str();
buffers.push_back(asio::buffer(request));
}
else {
buffers.push_back(status_strings::toBuffer(status)); buffers.push_back(status_strings::toBuffer(status));
}
for (std::size_t i = 0; i < headers.size(); ++i) { for (std::size_t i = 0; i < headers.size(); ++i) {
HTTPHeaders::Header& h = headers.m_headers[i]; HTTPHeaders::Header& h = headers.m_headers[i];
//::LogDebug(LOG_REST, "HTTPReply::toBuffers() header = %s, value = %s", h.name.c_str(), h.value.c_str()); //::LogDebug(LOG_REST, "HTTPPayload::toBuffers() header = %s, value = %s", h.name.c_str(), h.value.c_str());
buffers.push_back(asio::buffer(h.name)); buffers.push_back(asio::buffer(h.name));
buffers.push_back(asio::buffer(misc_strings::name_value_separator)); buffers.push_back(asio::buffer(misc_strings::name_value_separator));
@ -319,11 +328,11 @@ std::vector<asio::const_buffer> HTTPReply::toBuffers()
/// </summary> /// </summary>
/// <param name="obj"></param> /// <param name="obj"></param>
/// <param name="s"></param> /// <param name="s"></param>
void HTTPReply::reply(json::object obj, HTTPReply::StatusType s) void HTTPPayload::payload(json::object obj, HTTPPayload::StatusType s)
{ {
json::value v = json::value(obj); json::value v = json::value(obj);
std::string json = v.serialize(); std::string json = v.serialize();
reply(json, s, "application/json"); payload(json, s, "application/json");
} }
/// <summary> /// <summary>
@ -332,7 +341,7 @@ void HTTPReply::reply(json::object obj, HTTPReply::StatusType s)
/// <param name="c"></param> /// <param name="c"></param>
/// <param name="s"></param> /// <param name="s"></param>
/// <param name="contentType"></param> /// <param name="contentType"></param>
void HTTPReply::reply(std::string c, HTTPReply::StatusType s, std::string contentType) void HTTPPayload::payload(std::string c, HTTPPayload::StatusType s, std::string contentType)
{ {
content = c; content = c;
status = s; status = s;
@ -344,16 +353,34 @@ void HTTPReply::reply(std::string c, HTTPReply::StatusType s, std::string conten
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
/// <summary> /// <summary>
/// Get a stock reply. /// Get a status payload.
/// </summary>
/// <param name="method"></param>
/// <param name="uri"></param>
/// <param name="contentType"></param>
HTTPPayload HTTPPayload::requestPayload(std::string method, std::string uri, std::string contentType)
{
HTTPPayload rep;
rep.isClientPayload = true;
rep.method = ::strtoupper(method);
rep.uri = uri;
rep.ensureDefaultHeaders(contentType);
return rep;
}
/// <summary>
/// Get a status payload.
/// </summary> /// </summary>
/// <param name="status"></param> /// <param name="status"></param>
/// <param name="contentType"></param> /// <param name="contentType"></param>
HTTPReply HTTPReply::stockReply(HTTPReply::StatusType status, const std::string contentType) HTTPPayload HTTPPayload::statusPayload(HTTPPayload::StatusType status, const std::string contentType)
{ {
HTTPReply rep; HTTPPayload rep;
rep.isClientPayload = false;
rep.status = status; rep.status = status;
if (status != HTTPReply::NO_CONTENT) { if (status != HTTPPayload::NO_CONTENT) {
rep.content = stock_replies::to_string(status, contentType); rep.content = stock_replies::to_string(status, contentType);
rep.ensureDefaultHeaders(contentType); rep.ensureDefaultHeaders(contentType);
} }
@ -369,12 +396,22 @@ HTTPReply HTTPReply::stockReply(HTTPReply::StatusType status, const std::string
/// ///
/// </summary> /// </summary>
/// <param name="contentType"></param> /// <param name="contentType"></param>
void HTTPReply::ensureDefaultHeaders(std::string contentType) void HTTPPayload::ensureDefaultHeaders(std::string contentType)
{ {
if (!isClientPayload) {
headers.add("Content-Type", contentType); headers.add("Content-Type", contentType);
headers.add("Content-Length", std::to_string(content.size())); headers.add("Content-Length", std::to_string(content.size()));
headers.add("Server", std::string((__EXE_NAME__ "/" __VER__))); headers.add("Server", std::string((__EXE_NAME__ "/" __VER__)));
}
else {
headers.add("User-Agent", std::string((__EXE_NAME__ "/" __VER__)));
headers.add("Accept", "*/*");
if (::strtoupper(method) != HTTP_GET) {
headers.add("Content-Type", contentType);
headers.add("Content-Length", std::to_string(content.size()));
}
}
//for (auto header : headers.headers()) //for (auto header : headers.headers())
// ::LogDebug(LOG_REST, "HTTPReply::ensureDefaultHeaders() header = %s, value = %s", header.name.c_str(), header.value.c_str()); // ::LogDebug(LOG_REST, "HTTPPayload::ensureDefaultHeaders() header = %s, value = %s", header.name.c_str(), header.value.c_str());
} }

@ -53,12 +53,25 @@ namespace network
namespace http namespace http
{ {
// ---------------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------------
#define HTTP_GET "GET"
#define HTTP_POST "POST"
#define HTTP_PUT "PUT"
#define HTTP_DELETE "DELETE"
#define HTTP_OPTIONS "OPTIONS"
#define HTTP_DEFAULT_VERSION "HTTP/1.0"
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Structure Declaration // Structure Declaration
// This struct implements a model of a reply to be sent to a HTTP client. // This struct implements a model of a payload to be sent to a
// HTTP client/server.
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
struct HTTPReply struct HTTPPayload
{ {
/// <summary> /// <summary>
/// HTTP Status/Response Codes /// HTTP Status/Response Codes
@ -85,18 +98,28 @@ namespace network
HTTPHeaders headers; HTTPHeaders headers;
std::string content; std::string content;
/// <summary>Convert the reply into a vector of buffers. The buffers do not own the std::string method;
/// underlying memory blocks, therefore the reply object must remain valid and std::string uri;
int httpVersionMajor;
int httpVersionMinor;
bool isClientPayload = false;
/// <summary>Convert the payload into a vector of buffers. The buffers do not own the
/// underlying memory blocks, therefore the payload object must remain valid and
/// not be changed until the write operation has completed.</summary> /// not be changed until the write operation has completed.</summary>
std::vector<asio::const_buffer> toBuffers(); std::vector<asio::const_buffer> toBuffers();
/// <summary>Prepares reply for transmission by finalizing status and content type.</summary> /// <summary>Prepares payload for transmission by finalizing status and content type.</summary>
void reply(json::object obj, StatusType status = OK); void payload(json::object obj, StatusType status = OK);
/// <summary>Prepares reply for transmission by finalizing status and content type.</summary> /// <summary>Prepares payload for transmission by finalizing status and content type.</summary>
void reply(std::string content, StatusType status = OK, std::string contentType = "text/html"); void payload(std::string content, StatusType status = OK, std::string contentType = "text/html");
/// <summary>Get a stock reply.</summary> /// <summary>Get a request payload.</summary>
static HTTPReply stockReply(StatusType status, std::string contentType = "text/html"); static HTTPPayload requestPayload(std::string method, std::string uri, std::string contentType = "text/html");
/// <summary>Get a status payload.</summary>
static HTTPPayload statusPayload(StatusType status, std::string contentType = "text/html");
private: private:
/// <summary></summary> /// <summary></summary>

@ -1,74 +0,0 @@
/**
* 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/HTTPHeaders.h"
#include "Log.h"
#include <string>
#include <vector>
namespace network
{
namespace rest
{
namespace http
{
// ---------------------------------------------------------------------------
// Structure Declaration
// This struct implements a model of a request received from a HTTP client.
// ---------------------------------------------------------------------------
struct HTTPRequest
{
std::string method;
std::string uri;
HTTPHeaders headers;
std::string content;
int httpVersionMajor;
int httpVersionMinor;
};
} // namespace http
} // namespace rest
} // namespace network
#endif // __REST_HTTP__HTTP_REQUEST_H__

@ -36,8 +36,7 @@
*/ */
#include "Defines.h" #include "Defines.h"
#include "network/rest/http/HTTPRequestHandler.h" #include "network/rest/http/HTTPRequestHandler.h"
#include "network/rest/http/HTTPRequest.h" #include "network/rest/http/HTTPPayload.h"
#include "network/rest/http/HTTPReply.h"
using namespace network::rest::http; using namespace network::rest::http;
@ -62,19 +61,19 @@ HTTPRequestHandler::HTTPRequestHandler(const std::string& docRoot) :
/// <summary> /// <summary>
/// Handle a request and produce a reply. /// Handle a request and produce a reply.
/// </summary> /// </summary>
void HTTPRequestHandler::handleRequest(const HTTPRequest& request, HTTPReply& reply) void HTTPRequestHandler::handleRequest(const HTTPPayload& request, HTTPPayload& reply)
{ {
// decode url to path // decode url to path
std::string requestPath; std::string requestPath;
if (!urlDecode(request.uri, requestPath)) { if (!urlDecode(request.uri, requestPath)) {
reply = HTTPReply::stockReply(HTTPReply::BAD_REQUEST); reply = HTTPPayload::statusPayload(HTTPPayload::BAD_REQUEST);
return; return;
} }
// request path must be absolute and not contain "..". // request path must be absolute and not contain "..".
if (requestPath.empty() || requestPath[0] != '/' || if (requestPath.empty() || requestPath[0] != '/' ||
requestPath.find("..") != std::string::npos) { requestPath.find("..") != std::string::npos) {
reply = HTTPReply::stockReply(HTTPReply::BAD_REQUEST); reply = HTTPPayload::statusPayload(HTTPPayload::BAD_REQUEST);
return; return;
} }
@ -95,12 +94,12 @@ void HTTPRequestHandler::handleRequest(const HTTPRequest& request, HTTPReply& re
std::string fullPath = m_docRoot + requestPath; std::string fullPath = m_docRoot + requestPath;
std::ifstream is(fullPath.c_str(), std::ios::in | std::ios::binary); std::ifstream is(fullPath.c_str(), std::ios::in | std::ios::binary);
if (!is) { if (!is) {
reply = HTTPReply::stockReply(HTTPReply::NOT_FOUND); reply = HTTPPayload::statusPayload(HTTPPayload::NOT_FOUND);
return; return;
} }
// fill out the reply to be sent to the client // fill out the reply to be sent to the client
reply.status = HTTPReply::OK; reply.status = HTTPPayload::OK;
char buf[512]; char buf[512];
while (is.read(buf, sizeof(buf)).gcount() > 0) while (is.read(buf, sizeof(buf)).gcount() > 0)

@ -52,8 +52,7 @@ namespace network
// Class Prototypes // Class Prototypes
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
struct HTTPReply; struct HTTPPayload;
struct HTTPRequest;
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Class Declaration // Class Declaration
@ -76,7 +75,7 @@ namespace network
HTTPRequestHandler& operator=(HTTPRequestHandler&&) = default; HTTPRequestHandler& operator=(HTTPRequestHandler&&) = default;
/// <summary>Handle a request and produce a reply.</summary> /// <summary>Handle a request and produce a reply.</summary>
void handleRequest(const HTTPRequest& req, HTTPReply& reply); void handleRequest(const HTTPPayload& req, HTTPPayload& reply);
private: private:
/// <summary>Perform URL-decoding on a string. Returns false if the encoding was /// <summary>Perform URL-decoding on a string. Returns false if the encoding was

@ -41,8 +41,6 @@
#include "network/rest/http/Connection.h" #include "network/rest/http/Connection.h"
#include "network/rest/http/ConnectionManager.h" #include "network/rest/http/ConnectionManager.h"
#include "network/rest/http/HTTPRequestHandler.h" #include "network/rest/http/HTTPRequestHandler.h"
#include "network/rest/http/HTTPReply.h"
#include "network/rest/http/HTTPRequest.h"
#include <asio.hpp> #include <asio.hpp>

Loading…
Cancel
Save

Powered by TurnKey Linux.