diff --git a/CMakeLists.txt b/CMakeLists.txt index f65016f7..bee55de2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -118,6 +118,11 @@ file(GLOB dvmhost_SRC file(GLOB dvmcmd_SRC "network/UDPSocket.h" "network/UDPSocket.cpp" + "network/json/*.h" + "network/rest/*.h" + "network/rest/*.cpp" + "network/rest/http/*.h" + "network/rest/http/*.cpp" "remote/*.h" "remote/*.cpp" "edac/SHA256.h" diff --git a/Defines.h b/Defines.h index 16c2143a..22de6c86 100644 --- a/Defines.h +++ b/Defines.h @@ -236,6 +236,17 @@ inline std::string strtolower(const std::string value) { return v; } +/// +/// Helper to upper-case an input string. +/// +/// +/// +inline std::string strtoupper(const std::string value) { + std::string v = value; + std::transform(v.begin(), v.end(), v.begin(), ::toupper); + return v; +} + // --------------------------------------------------------------------------- // Macros // --------------------------------------------------------------------------- diff --git a/network/RESTAPI.cpp b/network/RESTAPI.cpp index a706c860..fdad64fc 100644 --- a/network/RESTAPI.cpp +++ b/network/RESTAPI.cpp @@ -70,7 +70,7 @@ using namespace modem; /// void setResponseDefaultStatus(json::object& obj) { - int s = (int)HTTPReply::OK; + int s = (int)HTTPPayload::OK; obj["status"].set(s); } @@ -80,9 +80,9 @@ void setResponseDefaultStatus(json::object& obj) /// /// /// -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; json::object response = json::object(); @@ -91,7 +91,7 @@ void errorReply(HTTPReply& reply, std::string message, HTTPReply::StatusType sta response["status"].set(s); response["message"].set(message); - reply.reply(response); + reply.payload(response); } /// @@ -101,11 +101,11 @@ void errorReply(HTTPReply& reply, std::string message, HTTPReply::StatusType sta /// /// /// -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"); if (contentType != "application/json") { - reply = HTTPReply::stockReply(HTTPReply::BAD_REQUEST, "application/json"); + reply = HTTPPayload::statusPayload(HTTPPayload::BAD_REQUEST, "application/json"); return false; } @@ -113,13 +113,13 @@ bool parseRequestBody(const HTTPRequest& request, HTTPReply& reply, json::object json::value v; std::string err = json::parse(v, request.content); if (!err.empty()) { - errorReply(reply, err); + errorPayload(reply, err); return false; } // ensure parsed JSON is an object if (!v.is()) { - errorReply(reply, "Request was not a valid JSON object."); + errorPayload(reply, "Request was not a valid JSON object."); return false; } @@ -313,12 +313,12 @@ void RESTAPI::invalidateHostToken(const std::string host) /// /// /// -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 headerToken = request.headers.find("X-DVM-Auth-Token"); if (headerToken == "") { - errorReply(reply, "invalid authentication token", HTTPReply::UNAUTHORIZED); + errorPayload(reply, "invalid authentication token", HTTPPayload::UNAUTHORIZED); return false; } @@ -330,12 +330,12 @@ bool RESTAPI::validateAuth(const HTTPRequest& request, HTTPReply& reply) return true; } else { m_authTokens.erase(host); // devalidate host - errorReply(reply, "invalid authentication token", HTTPReply::UNAUTHORIZED); + errorPayload(reply, "invalid authentication token", HTTPPayload::UNAUTHORIZED); return false; } } else { - errorReply(reply, "invalid authentication token", HTTPReply::UNAUTHORIZED); + errorPayload(reply, "invalid authentication token", HTTPPayload::UNAUTHORIZED); return false; } @@ -348,7 +348,7 @@ bool RESTAPI::validateAuth(const HTTPRequest& request, HTTPReply& reply) /// /// /// -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"); 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 if (!req["auth"].is()) { invalidateHostToken(host); - errorReply(reply, "password was not a valid string"); + errorPayload(reply, "password was not a valid string"); return; } std::string auth = req["auth"].get(); if (auth.empty()) { invalidateHostToken(host); - errorReply(reply, "auth cannot be empty"); + errorPayload(reply, "auth cannot be empty"); return; } if (auth.size() > 64) { invalidateHostToken(host); - errorReply(reply, "auth cannot be longer than 64 characters"); + errorPayload(reply, "auth cannot be longer than 64 characters"); return; } if (!(auth.find_first_not_of("0123456789abcdefABCDEF", 2) == std::string::npos)) { invalidateHostToken(host); - errorReply(reply, "auth contains invalid characters"); + errorPayload(reply, "auth contains invalid characters"); return; } @@ -406,7 +406,7 @@ void RESTAPI::restAPI_PutAuth(const HTTPRequest& request, HTTPReply& reply, cons // compare hashes if (::memcmp(m_passwordHash, passwordHash, 32U) != 0) { invalidateHostToken(host); - errorReply(reply, "invalid password"); + errorPayload(reply, "invalid password"); return; } @@ -418,7 +418,7 @@ void RESTAPI::restAPI_PutAuth(const HTTPRequest& request, HTTPReply& reply, cons m_authTokens[host] = salt; response["token"].set(std::to_string(salt)); - reply.reply(response); + reply.payload(response); } /// @@ -427,7 +427,7 @@ void RESTAPI::restAPI_PutAuth(const HTTPRequest& request, HTTPReply& reply, cons /// /// /// -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)) { return; @@ -437,7 +437,7 @@ void RESTAPI::restAPI_GetVersion(const HTTPRequest& request, HTTPReply& reply, c setResponseDefaultStatus(response); response["version"].set(std::string((__PROG_NAME__ " " __VER__ " (" DESCR_DMR DESCR_P25 DESCR_NXDN "CW Id, Network) (built " __BUILD__ ")"))); - reply.reply(response); + reply.payload(response); } /// @@ -446,7 +446,7 @@ void RESTAPI::restAPI_GetVersion(const HTTPRequest& request, HTTPReply& reply, c /// /// /// -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)) { return; @@ -563,7 +563,7 @@ void RESTAPI::restAPI_GetStatus(const HTTPRequest& request, HTTPReply& reply, co response["modem"].set(modemInfo); } - reply.reply(response); + reply.payload(response); } /// @@ -572,7 +572,7 @@ void RESTAPI::restAPI_GetStatus(const HTTPRequest& request, HTTPReply& reply, co /// /// /// -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)) { return; @@ -599,7 +599,7 @@ void RESTAPI::restAPI_GetVoiceCh(const HTTPRequest& request, HTTPReply& reply, c } response["channels"].set(channels); - reply.reply(response); + reply.payload(response); } /// @@ -608,7 +608,7 @@ void RESTAPI::restAPI_GetVoiceCh(const HTTPRequest& request, HTTPReply& reply, c /// /// /// -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)) { return; @@ -624,7 +624,7 @@ void RESTAPI::restAPI_PutModemMode(const HTTPRequest& request, HTTPReply& reply, // validate mode is a string within the JSON blob if (!req["mode"].is()) { - errorReply(reply, "password was not a valid string"); + errorPayload(reply, "password was not a valid string"); return; } @@ -638,7 +638,7 @@ void RESTAPI::restAPI_PutModemMode(const HTTPRequest& request, HTTPReply& reply, uint8_t hostMode = m_host->m_state; response["mode"].set(hostMode); - reply.reply(response); + reply.payload(response); } else if (mode == MODE_OPT_LCKOUT) { 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; response["mode"].set(hostMode); - reply.reply(response); + reply.payload(response); } #if defined(ENABLE_DMR) 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; response["mode"].set(hostMode); - reply.reply(response); + reply.payload(response); } 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) @@ -677,10 +677,10 @@ void RESTAPI::restAPI_PutModemMode(const HTTPRequest& request, HTTPReply& reply, uint8_t hostMode = m_host->m_state; response["mode"].set(hostMode); - reply.reply(response); + reply.payload(response); } 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) @@ -694,15 +694,15 @@ void RESTAPI::restAPI_PutModemMode(const HTTPRequest& request, HTTPReply& reply, uint8_t hostMode = m_host->m_state; response["mode"].set(hostMode); - reply.reply(response); + reply.payload(response); } 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) else { - errorReply(reply, "invalid mode"); + errorPayload(reply, "invalid mode"); } } @@ -712,7 +712,7 @@ void RESTAPI::restAPI_PutModemMode(const HTTPRequest& request, HTTPReply& reply, /// /// /// -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)) { return; @@ -723,7 +723,7 @@ void RESTAPI::restAPI_PutModemKill(const HTTPRequest& request, HTTPReply& reply, return; } - errorReply(reply, "OK", HTTPReply::OK); + errorPayload(reply, "OK", HTTPPayload::OK); if (!req["force"].is()) { g_killed = true; @@ -744,7 +744,7 @@ void RESTAPI::restAPI_PutModemKill(const HTTPRequest& request, HTTPReply& reply, /// /// /// -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)) { return; @@ -755,16 +755,16 @@ void RESTAPI::restAPI_PutPermitTG(const HTTPRequest& request, HTTPReply& reply, return; } - errorReply(reply, "OK", HTTPReply::OK); + errorPayload(reply, "OK", HTTPPayload::OK); if (!m_host->m_authoritative) { - errorReply(reply, "Host is authoritative, cannot permit TG"); + errorPayload(reply, "Host is authoritative, cannot permit TG"); return; } // validate state is a string within the JSON blob if (!req["state"].is()) { - errorReply(reply, "state was not a valid integer"); + errorPayload(reply, "state was not a valid integer"); return; } @@ -772,14 +772,14 @@ void RESTAPI::restAPI_PutPermitTG(const HTTPRequest& request, HTTPReply& reply, // validate destination ID is a integer within the JSON blob if (!req["dstId"].is()) { - errorReply(reply, "destination ID was not a valid integer"); + errorPayload(reply, "destination ID was not a valid integer"); return; } uint32_t dstId = req["dstId"].get(); if (dstId == 0U) { - errorReply(reply, "destination ID is an illegal TGID"); + errorPayload(reply, "destination ID is an illegal TGID"); return; } @@ -789,14 +789,14 @@ void RESTAPI::restAPI_PutPermitTG(const HTTPRequest& request, HTTPReply& reply, { // validate slot is a integer within the JSON blob if (!req["slot"].is()) { - errorReply(reply, "slot was not a valid integer"); + errorPayload(reply, "slot was not a valid integer"); return; } uint8_t slot = (uint8_t)req["slot"].get(); if (slot == 0U || slot > 2U) { - errorReply(reply, "illegal DMR slot"); + errorPayload(reply, "illegal DMR slot"); return; } @@ -804,12 +804,12 @@ void RESTAPI::restAPI_PutPermitTG(const HTTPRequest& request, HTTPReply& reply, m_dmr->permittedTG(dstId, slot); } else { - errorReply(reply, "DMR mode is not enabled", HTTPReply::SERVICE_UNAVAILABLE); + errorPayload(reply, "DMR mode is not enabled", HTTPPayload::SERVICE_UNAVAILABLE); } } #else { - errorReply(reply, "DMR operations are unavailable", HTTPReply::SERVICE_UNAVAILABLE); + errorPayload(reply, "DMR operations are unavailable", HTTPPayload::SERVICE_UNAVAILABLE); } #endif // defined(ENABLE_DMR) break; @@ -820,12 +820,12 @@ void RESTAPI::restAPI_PutPermitTG(const HTTPRequest& request, HTTPReply& reply, m_p25->permittedTG(dstId); } else { - errorReply(reply, "P25 mode is not enabled", HTTPReply::SERVICE_UNAVAILABLE); + errorPayload(reply, "P25 mode is not enabled", HTTPPayload::SERVICE_UNAVAILABLE); } } #else { - errorReply(reply, "P25 operations are unavailable", HTTPReply::SERVICE_UNAVAILABLE); + errorPayload(reply, "P25 operations are unavailable", HTTPPayload::SERVICE_UNAVAILABLE); } #endif // defined(ENABLE_P25) break; @@ -836,17 +836,17 @@ void RESTAPI::restAPI_PutPermitTG(const HTTPRequest& request, HTTPReply& reply, m_nxdn->permittedTG(dstId); } else { - errorReply(reply, "NXDN mode is not enabled", HTTPReply::SERVICE_UNAVAILABLE); + errorPayload(reply, "NXDN mode is not enabled", HTTPPayload::SERVICE_UNAVAILABLE); } } #else { - errorReply(reply, "NXDN operations are unavailable", HTTPReply::SERVICE_UNAVAILABLE); + errorPayload(reply, "NXDN operations are unavailable", HTTPPayload::SERVICE_UNAVAILABLE); } #endif // defined(ENABLE_NXDN) break; default: - errorReply(reply, "invalid mode"); + errorPayload(reply, "invalid mode"); break; } } @@ -857,7 +857,7 @@ void RESTAPI::restAPI_PutPermitTG(const HTTPRequest& request, HTTPReply& reply, /// /// /// -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)) { return; @@ -868,16 +868,16 @@ void RESTAPI::restAPI_PutGrantTG(const HTTPRequest& request, HTTPReply& reply, c 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)) { - errorReply(reply, "Host is authoritative, cannot grant TG"); + errorPayload(reply, "Host is authoritative, cannot grant TG"); return; } // validate state is a string within the JSON blob if (!req["state"].is()) { - errorReply(reply, "state was not a valid integer"); + errorPayload(reply, "state was not a valid integer"); 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 if (!req["dstId"].is()) { - errorReply(reply, "destination ID was not a valid integer"); + errorPayload(reply, "destination ID was not a valid integer"); return; } uint32_t dstId = req["dstId"].get(); if (dstId == 0U) { - errorReply(reply, "destination ID is an illegal TGID"); + errorPayload(reply, "destination ID is an illegal TGID"); return; } // validate unit-to-unit is a integer within the JSON blob if (!req["unitToUnit"].is()) { - errorReply(reply, "unit-to-unit was not a valid integer"); + errorPayload(reply, "unit-to-unit was not a valid integer"); return; } uint8_t unitToUnit = (uint8_t)req["unitToUnit"].get(); 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; } @@ -915,14 +915,14 @@ void RESTAPI::restAPI_PutGrantTG(const HTTPRequest& request, HTTPReply& reply, c { // validate slot is a integer within the JSON blob if (!req["slot"].is()) { - errorReply(reply, "slot was not a valid integer"); + errorPayload(reply, "slot was not a valid integer"); return; } uint8_t slot = (uint8_t)req["slot"].get(); if (slot == 0U || slot > 2U) { - errorReply(reply, "illegal DMR slot"); + errorPayload(reply, "illegal DMR slot"); return; } @@ -931,12 +931,12 @@ void RESTAPI::restAPI_PutGrantTG(const HTTPRequest& request, HTTPReply& reply, c //m_dmr->grantTG(dstId, slot, unitToUnit == 1U); } else { - errorReply(reply, "DMR mode is not enabled", HTTPReply::SERVICE_UNAVAILABLE); + errorPayload(reply, "DMR mode is not enabled", HTTPPayload::SERVICE_UNAVAILABLE); } } #else { - errorReply(reply, "DMR operations are unavailable", HTTPReply::SERVICE_UNAVAILABLE); + errorPayload(reply, "DMR operations are unavailable", HTTPPayload::SERVICE_UNAVAILABLE); } #endif // defined(ENABLE_DMR) break; @@ -948,12 +948,12 @@ void RESTAPI::restAPI_PutGrantTG(const HTTPRequest& request, HTTPReply& reply, c //m_p25->grantTG(dstId, unitToUnit == 1U); } else { - errorReply(reply, "P25 mode is not enabled", HTTPReply::SERVICE_UNAVAILABLE); + errorPayload(reply, "P25 mode is not enabled", HTTPPayload::SERVICE_UNAVAILABLE); } } #else { - errorReply(reply, "P25 operations are unavailable", HTTPReply::SERVICE_UNAVAILABLE); + errorPayload(reply, "P25 operations are unavailable", HTTPPayload::SERVICE_UNAVAILABLE); } #endif // defined(ENABLE_P25) break; @@ -965,17 +965,17 @@ void RESTAPI::restAPI_PutGrantTG(const HTTPRequest& request, HTTPReply& reply, c //nxdn->grantTG(dstId, unitToUnit == 1U); } else { - errorReply(reply, "NXDN mode is not enabled", HTTPReply::SERVICE_UNAVAILABLE); + errorPayload(reply, "NXDN mode is not enabled", HTTPPayload::SERVICE_UNAVAILABLE); } } #else { - errorReply(reply, "NXDN operations are unavailable", HTTPReply::SERVICE_UNAVAILABLE); + errorPayload(reply, "NXDN operations are unavailable", HTTPPayload::SERVICE_UNAVAILABLE); } #endif // defined(ENABLE_NXDN) break; default: - errorReply(reply, "invalid mode"); + errorPayload(reply, "invalid mode"); } } @@ -985,13 +985,13 @@ void RESTAPI::restAPI_PutGrantTG(const HTTPRequest& request, HTTPReply& reply, c /// /// /// -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)) { return; } - errorReply(reply, "OK", HTTPReply::OK); + errorPayload(reply, "OK", HTTPPayload::OK); #if defined(ENABLE_DMR) if (m_dmr != nullptr) { m_dmr->affiliations().releaseGrant(0, true); @@ -1015,13 +1015,13 @@ void RESTAPI::restAPI_GetReleaseGrants(const HTTPRequest& request, HTTPReply& re /// /// /// -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)) { return; } - errorReply(reply, "OK", HTTPReply::OK); + errorPayload(reply, "OK", HTTPPayload::OK); #if defined(ENABLE_DMR) if (m_dmr != nullptr) { m_dmr->affiliations().clearGroupAff(0, true); @@ -1045,25 +1045,25 @@ void RESTAPI::restAPI_GetReleaseAffs(const HTTPRequest& request, HTTPReply& repl /// /// /// -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)) { return; } if (match.size() < 2) { - errorReply(reply, "invalid API call arguments"); + errorPayload(reply, "invalid API call arguments"); return; } - errorReply(reply, "OK", HTTPReply::OK); + errorPayload(reply, "OK", HTTPPayload::OK); uint32_t srcId = (uint32_t)::strtoul(match.str(1).c_str(), NULL, 10); if (srcId != 0U) { m_ridLookup->toggleEntry(srcId, true); } 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 /// /// /// -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)) { return; } if (match.size() < 2) { - errorReply(reply, "invalid API call arguments"); + errorPayload(reply, "invalid API call arguments"); return; } - errorReply(reply, "OK", HTTPReply::OK); + errorPayload(reply, "OK", HTTPPayload::OK); uint32_t srcId = (uint32_t)::strtoul(match.str(1).c_str(), NULL, 10); if (srcId != 0U) { m_ridLookup->toggleEntry(srcId, false); } 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 /// /// /// -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)) { return; } #if defined(ENABLE_DMR) - errorReply(reply, "OK", HTTPReply::OK); + errorPayload(reply, "OK", HTTPPayload::OK); if (m_dmr != nullptr) { if (m_host->m_dmrBeacons) { g_fireDMRBeacon = true; } else { - errorReply(reply, "DMR beacons are not enabled", HTTPReply::SERVICE_UNAVAILABLE); + errorPayload(reply, "DMR beacons are not enabled", HTTPPayload::SERVICE_UNAVAILABLE); return; } } else { - errorReply(reply, "DMR mode is not enabled", HTTPReply::SERVICE_UNAVAILABLE); + errorPayload(reply, "DMR mode is not enabled", HTTPPayload::SERVICE_UNAVAILABLE); return; } #else - errorReply(reply, "DMR operations are unavailable", HTTPReply::SERVICE_UNAVAILABLE); + errorPayload(reply, "DMR operations are unavailable", HTTPPayload::SERVICE_UNAVAILABLE); #endif // defined(ENABLE_DMR) } @@ -1137,7 +1137,7 @@ void RESTAPI::restAPI_GetDMRBeacon(const HTTPRequest& request, HTTPReply& reply, /// /// /// -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)) { return; @@ -1146,7 +1146,7 @@ void RESTAPI::restAPI_GetDMRDebug(const HTTPRequest& request, HTTPReply& reply, json::object response = json::object(); setResponseDefaultStatus(response); #if defined(ENABLE_DMR) - errorReply(reply, "OK", HTTPReply::OK); + errorPayload(reply, "OK", HTTPPayload::OK); if (m_dmr != nullptr) { if (match.size() <= 1) { bool debug = m_dmr->getDebug(); @@ -1155,7 +1155,7 @@ void RESTAPI::restAPI_GetDMRDebug(const HTTPRequest& request, HTTPReply& reply, response["debug"].set(debug); response["verbose"].set(verbose); - reply.reply(response); + reply.payload(response); return; } else { @@ -1167,11 +1167,11 @@ void RESTAPI::restAPI_GetDMRDebug(const HTTPRequest& request, HTTPReply& reply, } } else { - errorReply(reply, "DMR mode is not enabled", HTTPReply::SERVICE_UNAVAILABLE); + errorPayload(reply, "DMR mode is not enabled", HTTPPayload::SERVICE_UNAVAILABLE); return; } #else - errorReply(reply, "DMR operations are unavailable", HTTPReply::SERVICE_UNAVAILABLE); + errorPayload(reply, "DMR operations are unavailable", HTTPPayload::SERVICE_UNAVAILABLE); #endif // defined(ENABLE_DMR) } @@ -1181,7 +1181,7 @@ void RESTAPI::restAPI_GetDMRDebug(const HTTPRequest& request, HTTPReply& reply, /// /// /// -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)) { return; @@ -1190,14 +1190,14 @@ void RESTAPI::restAPI_GetDMRDumpCSBK(const HTTPRequest& request, HTTPReply& repl json::object response = json::object(); setResponseDefaultStatus(response); #if defined(ENABLE_DMR) - errorReply(reply, "OK", HTTPReply::OK); + errorPayload(reply, "OK", HTTPPayload::OK); if (m_dmr != nullptr) { if (match.size() <= 1) { bool csbkDump = m_dmr->getCSBKVerbose(); response["verbose"].set(csbkDump); - reply.reply(response); + reply.payload(response); return; } else { @@ -1208,11 +1208,11 @@ void RESTAPI::restAPI_GetDMRDumpCSBK(const HTTPRequest& request, HTTPReply& repl } } else { - errorReply(reply, "DMR mode is not enabled", HTTPReply::SERVICE_UNAVAILABLE); + errorPayload(reply, "DMR mode is not enabled", HTTPPayload::SERVICE_UNAVAILABLE); return; } #else - errorReply(reply, "DMR operations are unavailable", HTTPReply::SERVICE_UNAVAILABLE); + errorPayload(reply, "DMR operations are unavailable", HTTPPayload::SERVICE_UNAVAILABLE); #endif // defined(ENABLE_DMR) } @@ -1222,7 +1222,7 @@ void RESTAPI::restAPI_GetDMRDumpCSBK(const HTTPRequest& request, HTTPReply& repl /// /// /// -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)) { return; @@ -1233,7 +1233,7 @@ void RESTAPI::restAPI_PutDMRRID(const HTTPRequest& request, HTTPReply& reply, co return; } - errorReply(reply, "OK", HTTPReply::OK); + errorPayload(reply, "OK", HTTPPayload::OK); } /// @@ -1242,18 +1242,18 @@ void RESTAPI::restAPI_PutDMRRID(const HTTPRequest& request, HTTPReply& reply, co /// /// /// -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)) { return; } if (match.size() < 2) { - errorReply(reply, "invalid API call arguments"); + errorPayload(reply, "invalid API call arguments"); return; } - errorReply(reply, "OK", HTTPReply::OK); + errorPayload(reply, "OK", HTTPPayload::OK); } /// @@ -1262,18 +1262,18 @@ void RESTAPI::restAPI_GetDMRCCEnable(const HTTPRequest& request, HTTPReply& repl /// /// /// -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)) { return; } if (match.size() < 2) { - errorReply(reply, "invalid API call arguments"); + errorPayload(reply, "invalid API call arguments"); return; } - errorReply(reply, "OK", HTTPReply::OK); + errorPayload(reply, "OK", HTTPPayload::OK); } /* @@ -1286,29 +1286,29 @@ void RESTAPI::restAPI_GetDMRCCBroadcast(const HTTPRequest& request, HTTPReply& r /// /// /// -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)) { return; } #if defined(ENABLE_P25) - errorReply(reply, "OK", HTTPReply::OK); + errorPayload(reply, "OK", HTTPPayload::OK); if (m_p25 != nullptr) { if (m_host->m_p25CCData) { g_fireP25Control = true; } else { - errorReply(reply, "P25 control data is not enabled", HTTPReply::SERVICE_UNAVAILABLE); + errorPayload(reply, "P25 control data is not enabled", HTTPPayload::SERVICE_UNAVAILABLE); return; } } else { - errorReply(reply, "P25 mode is not enabled", HTTPReply::SERVICE_UNAVAILABLE); + errorPayload(reply, "P25 mode is not enabled", HTTPPayload::SERVICE_UNAVAILABLE); return; } #else - errorReply(reply, "P25 operations are unavailable", HTTPReply::SERVICE_UNAVAILABLE); + errorPayload(reply, "P25 operations are unavailable", HTTPPayload::SERVICE_UNAVAILABLE); #endif // defined(ENABLE_P25) } @@ -1318,7 +1318,7 @@ void RESTAPI::restAPI_GetP25CC(const HTTPRequest& request, HTTPReply& reply, con /// /// /// -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)) { return; @@ -1327,7 +1327,7 @@ void RESTAPI::restAPI_GetP25Debug(const HTTPRequest& request, HTTPReply& reply, json::object response = json::object(); setResponseDefaultStatus(response); #if defined(ENABLE_P25) - errorReply(reply, "OK", HTTPReply::OK); + errorPayload(reply, "OK", HTTPPayload::OK); if (m_dmr != nullptr) { if (match.size() <= 1) { bool debug = m_p25->getDebug(); @@ -1336,7 +1336,7 @@ void RESTAPI::restAPI_GetP25Debug(const HTTPRequest& request, HTTPReply& reply, response["debug"].set(debug); response["verbose"].set(verbose); - reply.reply(response); + reply.payload(response); return; } else { @@ -1348,11 +1348,11 @@ void RESTAPI::restAPI_GetP25Debug(const HTTPRequest& request, HTTPReply& reply, } } else { - errorReply(reply, "P25 mode is not enabled", HTTPReply::SERVICE_UNAVAILABLE); + errorPayload(reply, "P25 mode is not enabled", HTTPPayload::SERVICE_UNAVAILABLE); return; } #else - errorReply(reply, "P25 operations are unavailable", HTTPReply::SERVICE_UNAVAILABLE); + errorPayload(reply, "P25 operations are unavailable", HTTPPayload::SERVICE_UNAVAILABLE); #endif // defined(ENABLE_P25) } @@ -1362,7 +1362,7 @@ void RESTAPI::restAPI_GetP25Debug(const HTTPRequest& request, HTTPReply& reply, /// /// /// -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)) { return; @@ -1371,14 +1371,14 @@ void RESTAPI::restAPI_GetP25DumpTSBK(const HTTPRequest& request, HTTPReply& repl json::object response = json::object(); setResponseDefaultStatus(response); #if defined(ENABLE_P25) - errorReply(reply, "OK", HTTPReply::OK); + errorPayload(reply, "OK", HTTPPayload::OK); if (m_p25 != nullptr) { if (match.size() <= 1) { bool tsbkDump = m_p25->trunk()->getTSBKVerbose(); response["verbose"].set(tsbkDump); - reply.reply(response); + reply.payload(response); return; } else { @@ -1389,11 +1389,11 @@ void RESTAPI::restAPI_GetP25DumpTSBK(const HTTPRequest& request, HTTPReply& repl } } else { - errorReply(reply, "P25 mode is not enabled", HTTPReply::SERVICE_UNAVAILABLE); + errorPayload(reply, "P25 mode is not enabled", HTTPPayload::SERVICE_UNAVAILABLE); return; } #else - errorReply(reply, "P25 operations are unavailable", HTTPReply::SERVICE_UNAVAILABLE); + errorPayload(reply, "P25 operations are unavailable", HTTPPayload::SERVICE_UNAVAILABLE); #endif // defined(ENABLE_P25) } @@ -1403,7 +1403,7 @@ void RESTAPI::restAPI_GetP25DumpTSBK(const HTTPRequest& request, HTTPReply& repl /// /// /// -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)) { return; @@ -1414,7 +1414,7 @@ void RESTAPI::restAPI_PutP25RID(const HTTPRequest& request, HTTPReply& reply, co return; } - errorReply(reply, "OK", HTTPReply::OK); + errorPayload(reply, "OK", HTTPPayload::OK); } /// @@ -1423,18 +1423,18 @@ void RESTAPI::restAPI_PutP25RID(const HTTPRequest& request, HTTPReply& reply, co /// /// /// -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)) { return; } if (match.size() < 2) { - errorReply(reply, "invalid API call arguments"); + errorPayload(reply, "invalid API call arguments"); return; } - errorReply(reply, "OK", HTTPReply::OK); + errorPayload(reply, "OK", HTTPPayload::OK); } /// @@ -1443,18 +1443,18 @@ void RESTAPI::restAPI_GetP25CCEnable(const HTTPRequest& request, HTTPReply& repl /// /// /// -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)) { return; } if (match.size() < 2) { - errorReply(reply, "invalid API call arguments"); + errorPayload(reply, "invalid API call arguments"); return; } - errorReply(reply, "OK", HTTPReply::OK); + errorPayload(reply, "OK", HTTPPayload::OK); } /* @@ -1467,7 +1467,7 @@ void RESTAPI::restAPI_GetP25CCBroadcast(const HTTPRequest& request, HTTPReply& r /// /// /// -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)) { return; @@ -1476,7 +1476,7 @@ void RESTAPI::restAPI_GetNXDNDebug(const HTTPRequest& request, HTTPReply& reply, json::object response = json::object(); setResponseDefaultStatus(response); #if defined(ENABLE_NXDN) - errorReply(reply, "OK", HTTPReply::OK); + errorPayload(reply, "OK", HTTPPayload::OK); if (m_dmr != nullptr) { if (match.size() <= 1) { bool debug = m_nxdn->getDebug(); @@ -1485,7 +1485,7 @@ void RESTAPI::restAPI_GetNXDNDebug(const HTTPRequest& request, HTTPReply& reply, response["debug"].set(debug); response["verbose"].set(verbose); - reply.reply(response); + reply.payload(response); return; } else { @@ -1497,11 +1497,11 @@ void RESTAPI::restAPI_GetNXDNDebug(const HTTPRequest& request, HTTPReply& reply, } } else { - errorReply(reply, "NXDN mode is not enabled", HTTPReply::SERVICE_UNAVAILABLE); + errorPayload(reply, "NXDN mode is not enabled", HTTPPayload::SERVICE_UNAVAILABLE); return; } #else - errorReply(reply, "NXDN operations are unavailable", HTTPReply::SERVICE_UNAVAILABLE); + errorPayload(reply, "NXDN operations are unavailable", HTTPPayload::SERVICE_UNAVAILABLE); #endif // defined(ENABLE_NXDN) } @@ -1511,7 +1511,7 @@ void RESTAPI::restAPI_GetNXDNDebug(const HTTPRequest& request, HTTPReply& reply, /// /// /// -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)) { return; @@ -1520,14 +1520,14 @@ void RESTAPI::restAPI_GetNXDNDumpRCCH(const HTTPRequest& request, HTTPReply& rep json::object response = json::object(); setResponseDefaultStatus(response); #if defined(ENABLE_NXDN) - errorReply(reply, "OK", HTTPReply::OK); + errorPayload(reply, "OK", HTTPPayload::OK); if (m_p25 != nullptr) { if (match.size() <= 1) { bool rcchDump = m_nxdn->getRCCHVerbose(); response["verbose"].set(rcchDump); - reply.reply(response); + reply.payload(response); return; } else { @@ -1538,10 +1538,10 @@ void RESTAPI::restAPI_GetNXDNDumpRCCH(const HTTPRequest& request, HTTPReply& rep } } else { - errorReply(reply, "NXDN mode is not enabled", HTTPReply::SERVICE_UNAVAILABLE); + errorPayload(reply, "NXDN mode is not enabled", HTTPPayload::SERVICE_UNAVAILABLE); return; } #else - errorReply(reply, "NXDN operations are unavailable", HTTPReply::SERVICE_UNAVAILABLE); + errorPayload(reply, "NXDN operations are unavailable", HTTPPayload::SERVICE_UNAVAILABLE); #endif // defined(ENABLE_NXDN) } diff --git a/network/RESTAPI.h b/network/RESTAPI.h index ab45018f..cc39f903 100644 --- a/network/RESTAPI.h +++ b/network/RESTAPI.h @@ -118,9 +118,8 @@ public: void close(); private: - typedef network::rest::RequestDispatcher RESTDispatcherType; - typedef network::rest::http::HTTPRequest HTTPRequest; - typedef network::rest::http::HTTPReply HTTPReply; + typedef network::rest::RequestDispatcher RESTDispatcherType; + typedef network::rest::http::HTTPPayload HTTPPayload; RESTDispatcherType m_dispatcher; network::rest::http::HTTPServer m_restServer; @@ -152,79 +151,79 @@ private: /// void invalidateHostToken(const std::string host); /// - bool validateAuth(const HTTPRequest& request, HTTPReply& reply); + bool validateAuth(const HTTPPayload& request, HTTPPayload& reply); /// - 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); /// - 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); /// - 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); /// - 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); /// - 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); /// - 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); /// - 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); /// - 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); /// - 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); /// - 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); /// - 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); /// - 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 */ /// - 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); /// - 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); /// - 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); /// - 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); /// - 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); /// - 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 */ /// - 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); /// - 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); /// - 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); /// - 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); /// - 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); /// - 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 */ /// - 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); /// - 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__ diff --git a/network/rest/RequestDispatcher.h b/network/rest/RequestDispatcher.h index 5d76f20c..d5aa3a54 100644 --- a/network/rest/RequestDispatcher.h +++ b/network/rest/RequestDispatcher.h @@ -27,8 +27,7 @@ #define __REST__DISPATCHER_H__ #include "Defines.h" -#include "network/rest/http/HTTPRequest.h" -#include "network/rest/http/HTTPReply.h" +#include "network/rest/http/HTTPPayload.h" #include "Log.h" #include @@ -68,27 +67,27 @@ namespace network /// RequestMatcher& get(RequestHandlerType handler) { - m_handlers["GET"] = handler; + m_handlers[HTTP_GET] = handler; return *this; } /// RequestMatcher& post(RequestHandlerType handler) { - m_handlers["POST"] = handler; + m_handlers[HTTP_POST] = handler; return *this; } /// RequestMatcher& put(RequestHandlerType handler) { - m_handlers["PUT"] = handler; + m_handlers[HTTP_PUT] = handler; return *this; } /// RequestMatcher& del(RequestHandlerType handler) { - m_handlers["DELETE"] = handler; + m_handlers[HTTP_DELETE] = handler; return *this; } /// RequestMatcher& options(RequestHandlerType handler) { - m_handlers["OPTIONS"] = handler; + m_handlers[HTTP_OPTIONS] = handler; return *this; } @@ -116,7 +115,7 @@ namespace network // This class implements RESTful web request dispatching. // --------------------------------------------------------------------------- - template + template class RequestDispatcher { typedef RequestMatcher MatcherType; public: @@ -174,7 +173,7 @@ namespace network } ::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: @@ -186,7 +185,7 @@ namespace network bool m_debug; }; - typedef RequestDispatcher DefaultRequestDispatcher; + typedef RequestDispatcher DefaultRequestDispatcher; } // namespace rest } // namespace network diff --git a/network/rest/http/Connection.h b/network/rest/http/Connection.h index e0b95af5..e8119898 100644 --- a/network/rest/http/Connection.h +++ b/network/rest/http/Connection.h @@ -38,9 +38,8 @@ #define __REST_HTTP__CONNECTION_H__ #include "Defines.h" -#include "network/rest/http/HTTPRequest.h" -#include "network/rest/http/HTTPRequestLexer.h" -#include "network/rest/http/HTTPReply.h" +#include "network/rest/http/HTTPLexer.h" +#include "network/rest/http/HTTPPayload.h" #include #include @@ -75,11 +74,13 @@ namespace network public: /// Initializes a new instance of the Connection class. 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_connectionManager(manager), m_requestHandler(handler), - m_persistent(persistent) + m_lexer(HTTPLexer(client)), + m_persistent(persistent), + m_client(client) { /* stub */ } @@ -88,7 +89,7 @@ namespace network /// Connection& operator=(const Connection&) = delete; - + /// Start the first asynchronous operation for the connection. void start() { read(); } /// Stop all asynchronous operations associated with the connection. @@ -103,7 +104,7 @@ namespace network m_socket.async_read_some(asio::buffer(m_buffer), [=](asio::error_code ec, std::size_t bytes_transferred) { if (!ec) { - HTTPRequestLexer::ResultType result; + HTTPLexer::ResultType result; char* content; std::tie(result, content) = m_lexer.parse(m_request, m_buffer.data(), m_buffer.data() + bytes_transferred); @@ -114,20 +115,30 @@ namespace network m_request.content = std::string(content, length); } - if (result == HTTPRequestLexer::GOOD) { + if (m_client) { m_requestHandler.handleRequest(m_request, m_reply); - write(); - } - else if (result == HTTPRequestLexer::BAD) { - m_reply = HTTPReply::stockReply(HTTPReply::BAD_REQUEST); - write(); } else { - read(); + if (result == HTTPLexer::GOOD) { + m_requestHandler.handleRequest(m_request, m_reply); + write(); + } + else if (result == HTTPLexer::BAD) { + m_reply = HTTPPayload::statusPayload(HTTPPayload::BAD_REQUEST); + write(); + } + else { + read(); + } } } else if (ec != asio::error::operation_aborted) { - m_connectionManager.stop(this->shared_from_this()); + if (m_client) { + m_socket.close(); + } + else { + m_connectionManager.stop(this->shared_from_this()); + } } }); } @@ -135,6 +146,10 @@ namespace network /// Perform an asynchronous write operation. void write() { + if (m_client) { + return; + } + if (!m_persistent) { auto self(this->shared_from_this()); } else { @@ -145,9 +160,9 @@ namespace network if (m_persistent) { m_lexer.reset(); m_reply.headers = HTTPHeaders(); - m_reply.status = HTTPReply::OK; + m_reply.status = HTTPPayload::OK; m_reply.content = ""; - m_request = HTTPRequest(); + m_request = HTTPPayload(); read(); } else @@ -166,13 +181,18 @@ namespace network } asio::ip::tcp::socket m_socket; + ConnectionManagerType& m_connectionManager; RequestHandlerType& m_requestHandler; + std::array m_buffer; - HTTPRequest m_request; - HTTPRequestLexer m_lexer; - HTTPReply m_reply; + + HTTPPayload m_request; + HTTPLexer m_lexer; + HTTPPayload m_reply; + bool m_persistent; + bool m_client; }; } // namespace http } // namespace rest diff --git a/network/rest/http/HTTPClient.h b/network/rest/http/HTTPClient.h new file mode 100644 index 00000000..86a7755b --- /dev/null +++ b/network/rest/http/HTTPClient.h @@ -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 + +#include +#include +#include +#include +#include +#include + +namespace network +{ + namespace rest + { + namespace http + { + + // --------------------------------------------------------------------------- + // Class Declaration + // This class implements top-level routines of the HTTP client. + // --------------------------------------------------------------------------- + + template class ConnectionImpl = Connection> + class HTTPClient : private Thread { + public: + /// Initializes a new instance of the HTTPClient class. + 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 */ + } + /// Initializes a copy instance of the HTTPClient class. + HTTPClient(const HTTPClient&) = delete; + + /// + HTTPClient& operator=(const HTTPClient&) = delete; + + /// Helper to set the HTTP request handlers. + template + void setHandler(Handler&& handler) + { + m_requestHandler = RequestHandlerType(std::forward(handler)); + } + + /// Send HTTP request to HTTP server. + void request(HTTPPayload& request) + { + asio::post(m_ioContext, [this, request]() { + std::lock_guard guard(m_lock); + { + write(request); + } + }); + } + + /// Opens connection to the network. + bool open() + { + return run(); + } + + /// Closes connection to the network. + void close() + { + if (m_connection != nullptr) { + m_connection.stop(); + } + + wait(); + } + + private: + /// + 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(); + } + + /// Perform an asynchronous connect operation. + void connect(asio::ip::basic_resolver_results& endpoints) + { + asio::async_connect(m_socket, endpoints, [this](asio::error_code ec, asio::ip::tcp::endpoint) { + if (!ec) { + m_connection = std::make_shared(std::move(m_socket), m_connectionManager, m_requestHandler, false, true); + m_connection->start(); + } + }); + } + + /// Perform an asynchronous write operation. + 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 ConnectionType; + typedef std::shared_ptr ConnectionTypePtr; + + ConnectionTypePtr m_connection; + + asio::io_context m_ioContext; + + ConnectionManager 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__ diff --git a/network/rest/http/HTTPHeaders.h b/network/rest/http/HTTPHeaders.h index 8ca10d46..146aba48 100644 --- a/network/rest/http/HTTPHeaders.h +++ b/network/rest/http/HTTPHeaders.h @@ -54,7 +54,7 @@ namespace network // Class Prototypes // --------------------------------------------------------------------------- - struct HTTPReply; + struct HTTPPayload; // --------------------------------------------------------------------------- // Structure Declaration @@ -122,7 +122,7 @@ namespace network } private: - friend struct HTTPReply; + friend struct HTTPPayload; std::vector
m_headers; }; } // namespace http diff --git a/network/rest/http/HTTPRequestLexer.cpp b/network/rest/http/HTTPLexer.cpp similarity index 77% rename from network/rest/http/HTTPRequestLexer.cpp rename to network/rest/http/HTTPLexer.cpp index 70cd5adb..ba483b74 100644 --- a/network/rest/http/HTTPRequestLexer.cpp +++ b/network/rest/http/HTTPLexer.cpp @@ -35,8 +35,8 @@ * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "Defines.h" -#include "network/rest/http/HTTPRequestLexer.h" -#include "network/rest/http/HTTPRequest.h" +#include "network/rest/http/HTTPLexer.h" +#include "network/rest/http/HTTPPayload.h" #include "Log.h" using namespace network::rest::http; @@ -48,20 +48,27 @@ using namespace network::rest::http; // --------------------------------------------------------------------------- /// -/// Initializes a new instance of the HTTPRequestLexer class. +/// Initializes a new instance of the HTTPLexer class. /// - -HTTPRequestLexer::HTTPRequestLexer() : +/// +HTTPLexer::HTTPLexer(bool clientLexer) : m_headers(), + m_clientLexer(clientLexer), m_state(METHOD_START) { - /* stub */ + if (m_clientLexer) { + m_state = HTTP_VERSION_H; + } } /// Reset to initial parser state. -void HTTPRequestLexer::reset() +void HTTPLexer::reset() { m_state = METHOD_START; + if (m_clientLexer) { + m_state = HTTP_VERSION_H; + } + m_headers = std::vector(); } @@ -75,7 +82,7 @@ void HTTPRequestLexer::reset() /// /// /// -HTTPRequestLexer::ResultType HTTPRequestLexer::consume(HTTPRequest& req, char input) +HTTPLexer::ResultType HTTPLexer::consume(HTTPPayload& req, char input) { switch (m_state) { @@ -123,6 +130,7 @@ HTTPRequestLexer::ResultType HTTPRequestLexer::consume(HTTPRequest& req, char in /* ** HTTP/1.0 + ** HTTP/1.0 200 OK */ case HTTP_VERSION_H: if (input == 'H') { @@ -197,19 +205,86 @@ HTTPRequestLexer::ResultType HTTPRequestLexer::consume(HTTPRequest& req, char in return BAD; } case HTTP_VERSION_MINOR: - if (input == '\r') - { + if (input == '\r') { m_state = EXPECTING_NEWLINE_1; - return INDETERMINATE; + if (m_clientLexer) { + return BAD; + } + else { + 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'; return INDETERMINATE; } else { 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: if (input == '\n') { @@ -275,8 +350,7 @@ HTTPRequestLexer::ResultType HTTPRequestLexer::consume(HTTPRequest& req, char in } case SPACE_BEFORE_HEADER_VALUE: - if (input == ' ') - { + if (input == ' ') { m_state = HEADER_VALUE; return INDETERMINATE; } @@ -309,7 +383,7 @@ HTTPRequestLexer::ResultType HTTPRequestLexer::consume(HTTPRequest& req, char in case EXPECTING_NEWLINE_3: if (input == '\n') { 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); } @@ -328,7 +402,7 @@ HTTPRequestLexer::ResultType HTTPRequestLexer::consume(HTTPRequest& req, char in ///
/// /// -bool HTTPRequestLexer::isChar(int c) +bool HTTPLexer::isChar(int c) { return c >= 0 && c <= 127; } @@ -338,7 +412,7 @@ bool HTTPRequestLexer::isChar(int c) ///
/// /// -bool HTTPRequestLexer::isControl(int c) +bool HTTPLexer::isControl(int c) { return (c >= 0 && c <= 31) || (c == 127); } @@ -348,7 +422,7 @@ bool HTTPRequestLexer::isControl(int c) ///
/// /// -bool HTTPRequestLexer::isSpecial(int c) +bool HTTPLexer::isSpecial(int c) { switch (c) { @@ -367,7 +441,7 @@ bool HTTPRequestLexer::isSpecial(int c) ///
/// /// -bool HTTPRequestLexer::isDigit(int c) +bool HTTPLexer::isDigit(int c) { return c >= '0' && c <= '9'; } diff --git a/network/rest/http/HTTPRequestLexer.h b/network/rest/http/HTTPLexer.h similarity index 87% rename from network/rest/http/HTTPRequestLexer.h rename to network/rest/http/HTTPLexer.h index 769b607b..9a805a2e 100644 --- a/network/rest/http/HTTPRequestLexer.h +++ b/network/rest/http/HTTPLexer.h @@ -34,8 +34,10 @@ * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -#if !defined(__REST_HTTP__HTTP_REQUEST_PARSER_H__) -#define __REST_HTTP__HTTP_REQUEST_PARSER_H__ +#if !defined(__REST_HTTP__HTTP_LEXER_H__) +#define __REST_HTTP__HTTP_LEXER_H__ + +#include "Defines.h" #include #include @@ -51,20 +53,20 @@ namespace network // Class Prototypes // --------------------------------------------------------------------------- - struct HTTPRequest; + struct HTTPPayload; // --------------------------------------------------------------------------- // Class Declaration - // This class implements the lexer for incoming requests. + // This class implements the lexer for incoming payloads. // --------------------------------------------------------------------------- - class HTTPRequestLexer + class HTTPLexer { public: enum ResultType { GOOD, BAD, INDETERMINATE }; - /// Initializes a new instance of the HTTPRequestLexer class. - HTTPRequestLexer(); + /// Initializes a new instance of the HTTPLexer class. + HTTPLexer(bool clientLexer); /// Reset to initial parser state. void reset(); @@ -74,10 +76,10 @@ namespace network /// required. The InputIterator return value indicates how much of the input /// has been consumed.
template - std::tuple parse(HTTPRequest& req, InputIterator begin, InputIterator end) + std::tuple parse(HTTPPayload& payload, InputIterator begin, InputIterator end) { while (begin != end) { - ResultType result = consume(req, *begin++); + ResultType result = consume(payload, *begin++); if (result == GOOD || result == BAD) return std::make_tuple(result, begin); } @@ -86,7 +88,7 @@ namespace network private: /// Handle the next character of input. - ResultType consume(HTTPRequest& req, char input); + ResultType consume(HTTPPayload& payload, char input); /// Check if a byte is an HTTP character. static bool isChar(int c); @@ -107,6 +109,8 @@ namespace network }; std::vector m_headers; + uint16_t m_status; + bool m_clientLexer = false; enum state { @@ -122,6 +126,12 @@ namespace network HTTP_VERSION_MAJOR, HTTP_VERSION_MINOR_START, 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, HEADER_LINE_START, HEADER_LWS, @@ -136,4 +146,4 @@ namespace network } // namespace rest } // namespace network -#endif // __REST_HTTP__HTTP_REQUEST_PARSER_H__ +#endif // __REST_HTTP__HTTP_LEXER_H__ diff --git a/network/rest/http/HTTPReply.cpp b/network/rest/http/HTTPPayload.cpp similarity index 73% rename from network/rest/http/HTTPReply.cpp rename to network/rest/http/HTTPPayload.cpp index 3922a97b..3ef52f3c 100644 --- a/network/rest/http/HTTPReply.cpp +++ b/network/rest/http/HTTPPayload.cpp @@ -35,7 +35,7 @@ * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "Defines.h" -#include "network/rest/http/HTTPReply.h" +#include "network/rest/http/HTTPPayload.h" #include "Log.h" 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 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) { - case HTTPReply::OK: + case HTTPPayload::OK: return asio::buffer(ok); - case HTTPReply::CREATED: + case HTTPPayload::CREATED: return asio::buffer(created); - case HTTPReply::ACCEPTED: + case HTTPPayload::ACCEPTED: return asio::buffer(accepted); - case HTTPReply::NO_CONTENT: + case HTTPPayload::NO_CONTENT: return asio::buffer(no_content); - case HTTPReply::MULTIPLE_CHOICES: + case HTTPPayload::MULTIPLE_CHOICES: return asio::buffer(multiple_choices); - case HTTPReply::MOVED_PERMANENTLY: + case HTTPPayload::MOVED_PERMANENTLY: return asio::buffer(moved_permanently); - case HTTPReply::MOVED_TEMPORARILY: + case HTTPPayload::MOVED_TEMPORARILY: return asio::buffer(moved_temporarily); - case HTTPReply::NOT_MODIFIED: + case HTTPPayload::NOT_MODIFIED: return asio::buffer(not_modified); - case HTTPReply::BAD_REQUEST: + case HTTPPayload::BAD_REQUEST: return asio::buffer(bad_request); - case HTTPReply::UNAUTHORIZED: + case HTTPPayload::UNAUTHORIZED: return asio::buffer(unauthorized); - case HTTPReply::FORBIDDEN: + case HTTPPayload::FORBIDDEN: return asio::buffer(forbidden); - case HTTPReply::NOT_FOUND: + case HTTPPayload::NOT_FOUND: return asio::buffer(not_found); - case HTTPReply::INTERNAL_SERVER_ERROR: + case HTTPPayload::INTERNAL_SERVER_ERROR: return asio::buffer(internal_server_error); - case HTTPReply::NOT_IMPLEMENTED: + case HTTPPayload::NOT_IMPLEMENTED: return asio::buffer(not_implemented); - case HTTPReply::BAD_GATEWAY: + case HTTPPayload::BAD_GATEWAY: return asio::buffer(bad_gateway); - case HTTPReply::SERVICE_UNAVAILABLE: + case HTTPPayload::SERVICE_UNAVAILABLE: return asio::buffer(service_unavailable); default: return asio::buffer(internal_server_error); @@ -201,43 +201,43 @@ namespace stock_replies { ""; 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); if (contentType == "application/json") { switch (status) { - case HTTPReply::OK: + case HTTPPayload::OK: return json_ok; - case HTTPReply::CREATED: + case HTTPPayload::CREATED: return json_created; - case HTTPReply::ACCEPTED: + case HTTPPayload::ACCEPTED: return json_accepted; - case HTTPReply::NO_CONTENT: + case HTTPPayload::NO_CONTENT: return json_no_content; - case HTTPReply::MULTIPLE_CHOICES: + case HTTPPayload::MULTIPLE_CHOICES: return json_multiple_choices; - case HTTPReply::MOVED_PERMANENTLY: + case HTTPPayload::MOVED_PERMANENTLY: return json_moved_permanently; - case HTTPReply::MOVED_TEMPORARILY: + case HTTPPayload::MOVED_TEMPORARILY: return json_moved_temporarily; - case HTTPReply::NOT_MODIFIED: + case HTTPPayload::NOT_MODIFIED: return json_not_modified; - case HTTPReply::BAD_REQUEST: + case HTTPPayload::BAD_REQUEST: return json_bad_request; - case HTTPReply::UNAUTHORIZED: + case HTTPPayload::UNAUTHORIZED: return json_unauthorized; - case HTTPReply::FORBIDDEN: + case HTTPPayload::FORBIDDEN: return json_forbidden; - case HTTPReply::NOT_FOUND: + case HTTPPayload::NOT_FOUND: return json_not_found; - case HTTPReply::INTERNAL_SERVER_ERROR: + case HTTPPayload::INTERNAL_SERVER_ERROR: return json_internal_server_error; - case HTTPReply::NOT_IMPLEMENTED: + case HTTPPayload::NOT_IMPLEMENTED: return json_not_implemented; - case HTTPReply::BAD_GATEWAY: + case HTTPPayload::BAD_GATEWAY: return json_bad_gateway; - case HTTPReply::SERVICE_UNAVAILABLE: + case HTTPPayload::SERVICE_UNAVAILABLE: return json_service_unavailable; default: return json_internal_server_error; @@ -246,37 +246,37 @@ namespace stock_replies { else { switch (status) { - case HTTPReply::OK: + case HTTPPayload::OK: return ok; - case HTTPReply::CREATED: + case HTTPPayload::CREATED: return created; - case HTTPReply::ACCEPTED: + case HTTPPayload::ACCEPTED: return accepted; - case HTTPReply::NO_CONTENT: + case HTTPPayload::NO_CONTENT: return no_content; - case HTTPReply::MULTIPLE_CHOICES: + case HTTPPayload::MULTIPLE_CHOICES: return multiple_choices; - case HTTPReply::MOVED_PERMANENTLY: + case HTTPPayload::MOVED_PERMANENTLY: return moved_permanently; - case HTTPReply::MOVED_TEMPORARILY: + case HTTPPayload::MOVED_TEMPORARILY: return moved_temporarily; - case HTTPReply::NOT_MODIFIED: + case HTTPPayload::NOT_MODIFIED: return not_modified; - case HTTPReply::BAD_REQUEST: + case HTTPPayload::BAD_REQUEST: return bad_request; - case HTTPReply::UNAUTHORIZED: + case HTTPPayload::UNAUTHORIZED: return unauthorized; - case HTTPReply::FORBIDDEN: + case HTTPPayload::FORBIDDEN: return forbidden; - case HTTPReply::NOT_FOUND: + case HTTPPayload::NOT_FOUND: return not_found; - case HTTPReply::INTERNAL_SERVER_ERROR: + case HTTPPayload::INTERNAL_SERVER_ERROR: return internal_server_error; - case HTTPReply::NOT_IMPLEMENTED: + case HTTPPayload::NOT_IMPLEMENTED: return not_implemented; - case HTTPReply::BAD_GATEWAY: + case HTTPPayload::BAD_GATEWAY: return bad_gateway; - case HTTPReply::SERVICE_UNAVAILABLE: + case HTTPPayload::SERVICE_UNAVAILABLE: return service_unavailable; default: return internal_server_error; @@ -294,14 +294,23 @@ namespace stock_replies { /// underlying memory blocks, therefore the reply object must remain valid and /// not be changed until the write operation has completed. ///
-std::vector HTTPReply::toBuffers() +std::vector HTTPPayload::toBuffers() { std::vector buffers; - buffers.push_back(status_strings::toBuffer(status)); + 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)); + } for (std::size_t i = 0; i < headers.size(); ++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(misc_strings::name_value_separator)); @@ -319,11 +328,11 @@ std::vector HTTPReply::toBuffers() ///
/// /// -void HTTPReply::reply(json::object obj, HTTPReply::StatusType s) +void HTTPPayload::payload(json::object obj, HTTPPayload::StatusType s) { json::value v = json::value(obj); std::string json = v.serialize(); - reply(json, s, "application/json"); + payload(json, s, "application/json"); } /// @@ -332,7 +341,7 @@ void HTTPReply::reply(json::object obj, HTTPReply::StatusType s) /// /// /// -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; status = s; @@ -344,16 +353,34 @@ void HTTPReply::reply(std::string c, HTTPReply::StatusType s, std::string conten // --------------------------------------------------------------------------- /// -/// Get a stock reply. +/// Get a status payload. +/// +/// +/// +/// +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; +} + +/// +/// Get a status payload. /// /// /// -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; - if (status != HTTPReply::NO_CONTENT) { + if (status != HTTPPayload::NO_CONTENT) { rep.content = stock_replies::to_string(status, contentType); rep.ensureDefaultHeaders(contentType); } @@ -369,12 +396,22 @@ HTTPReply HTTPReply::stockReply(HTTPReply::StatusType status, const std::string /// /// /// -void HTTPReply::ensureDefaultHeaders(std::string contentType) +void HTTPPayload::ensureDefaultHeaders(std::string contentType) { - headers.add("Content-Type", contentType); - headers.add("Content-Length", std::to_string(content.size())); - headers.add("Server", std::string((__EXE_NAME__ "/" __VER__))); + if (!isClientPayload) { + headers.add("Content-Type", contentType); + headers.add("Content-Length", std::to_string(content.size())); + 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()) - // ::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()); } diff --git a/network/rest/http/HTTPReply.h b/network/rest/http/HTTPPayload.h similarity index 67% rename from network/rest/http/HTTPReply.h rename to network/rest/http/HTTPPayload.h index 889f7ae0..745d0d46 100644 --- a/network/rest/http/HTTPReply.h +++ b/network/rest/http/HTTPPayload.h @@ -53,12 +53,25 @@ namespace network 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 - // 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 { /// /// HTTP Status/Response Codes @@ -85,18 +98,28 @@ namespace network HTTPHeaders headers; std::string content; - /// Convert the reply into a vector of buffers. The buffers do not own the - /// underlying memory blocks, therefore the reply object must remain valid and + std::string method; + std::string uri; + + int httpVersionMajor; + int httpVersionMinor; + + bool isClientPayload = false; + + /// 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. std::vector toBuffers(); - /// Prepares reply for transmission by finalizing status and content type. - void reply(json::object obj, StatusType status = OK); - /// Prepares reply for transmission by finalizing status and content type. - void reply(std::string content, StatusType status = OK, std::string contentType = "text/html"); + /// Prepares payload for transmission by finalizing status and content type. + void payload(json::object obj, StatusType status = OK); + /// Prepares payload for transmission by finalizing status and content type. + void payload(std::string content, StatusType status = OK, std::string contentType = "text/html"); - /// Get a stock reply. - static HTTPReply stockReply(StatusType status, std::string contentType = "text/html"); + /// Get a request payload. + static HTTPPayload requestPayload(std::string method, std::string uri, std::string contentType = "text/html"); + /// Get a status payload. + static HTTPPayload statusPayload(StatusType status, std::string contentType = "text/html"); private: /// diff --git a/network/rest/http/HTTPRequest.h b/network/rest/http/HTTPRequest.h deleted file mode 100644 index 8bf8284a..00000000 --- a/network/rest/http/HTTPRequest.h +++ /dev/null @@ -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 -#include - -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__ diff --git a/network/rest/http/HTTPRequestHandler.cpp b/network/rest/http/HTTPRequestHandler.cpp index 6cffc390..cb3a5dd2 100644 --- a/network/rest/http/HTTPRequestHandler.cpp +++ b/network/rest/http/HTTPRequestHandler.cpp @@ -36,8 +36,7 @@ */ #include "Defines.h" #include "network/rest/http/HTTPRequestHandler.h" -#include "network/rest/http/HTTPRequest.h" -#include "network/rest/http/HTTPReply.h" +#include "network/rest/http/HTTPPayload.h" using namespace network::rest::http; @@ -62,19 +61,19 @@ HTTPRequestHandler::HTTPRequestHandler(const std::string& docRoot) : /// /// Handle a request and produce a reply. /// -void HTTPRequestHandler::handleRequest(const HTTPRequest& request, HTTPReply& reply) +void HTTPRequestHandler::handleRequest(const HTTPPayload& request, HTTPPayload& reply) { // decode url to path std::string requestPath; if (!urlDecode(request.uri, requestPath)) { - reply = HTTPReply::stockReply(HTTPReply::BAD_REQUEST); + reply = HTTPPayload::statusPayload(HTTPPayload::BAD_REQUEST); return; } // request path must be absolute and not contain "..". if (requestPath.empty() || requestPath[0] != '/' || requestPath.find("..") != std::string::npos) { - reply = HTTPReply::stockReply(HTTPReply::BAD_REQUEST); + reply = HTTPPayload::statusPayload(HTTPPayload::BAD_REQUEST); return; } @@ -95,12 +94,12 @@ void HTTPRequestHandler::handleRequest(const HTTPRequest& request, HTTPReply& re std::string fullPath = m_docRoot + requestPath; std::ifstream is(fullPath.c_str(), std::ios::in | std::ios::binary); if (!is) { - reply = HTTPReply::stockReply(HTTPReply::NOT_FOUND); + reply = HTTPPayload::statusPayload(HTTPPayload::NOT_FOUND); return; } // fill out the reply to be sent to the client - reply.status = HTTPReply::OK; + reply.status = HTTPPayload::OK; char buf[512]; while (is.read(buf, sizeof(buf)).gcount() > 0) diff --git a/network/rest/http/HTTPRequestHandler.h b/network/rest/http/HTTPRequestHandler.h index 6cc7a35e..b6e96b78 100644 --- a/network/rest/http/HTTPRequestHandler.h +++ b/network/rest/http/HTTPRequestHandler.h @@ -52,8 +52,7 @@ namespace network // Class Prototypes // --------------------------------------------------------------------------- - struct HTTPReply; - struct HTTPRequest; + struct HTTPPayload; // --------------------------------------------------------------------------- // Class Declaration @@ -76,7 +75,7 @@ namespace network HTTPRequestHandler& operator=(HTTPRequestHandler&&) = default; /// Handle a request and produce a reply. - void handleRequest(const HTTPRequest& req, HTTPReply& reply); + void handleRequest(const HTTPPayload& req, HTTPPayload& reply); private: /// Perform URL-decoding on a string. Returns false if the encoding was diff --git a/network/rest/http/HTTPServer.h b/network/rest/http/HTTPServer.h index be78362c..eb9640a6 100644 --- a/network/rest/http/HTTPServer.h +++ b/network/rest/http/HTTPServer.h @@ -41,8 +41,6 @@ #include "network/rest/http/Connection.h" #include "network/rest/http/ConnectionManager.h" #include "network/rest/http/HTTPRequestHandler.h" -#include "network/rest/http/HTTPReply.h" -#include "network/rest/http/HTTPRequest.h" #include