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
"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"

@ -236,6 +236,17 @@ inline std::string strtolower(const std::string value) {
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
// ---------------------------------------------------------------------------

@ -70,7 +70,7 @@ using namespace modem;
/// <param name="obj"></param>
void setResponseDefaultStatus(json::object& obj)
{
int s = (int)HTTPReply::OK;
int s = (int)HTTPPayload::OK;
obj["status"].set<int>(s);
}
@ -80,9 +80,9 @@ void setResponseDefaultStatus(json::object& obj)
/// <param name="reply"></param>
/// <param name="message"></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;
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["message"].set<std::string>(message);
reply.reply(response);
reply.payload(response);
}
/// <summary>
@ -101,11 +101,11 @@ void errorReply(HTTPReply& reply, std::string message, HTTPReply::StatusType sta
/// <param name="reply"></param>
/// <param name="obj"></param>
/// <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");
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<json::object>()) {
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)
///
/// </summary>
/// <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 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)
/// <param name="request"></param>
/// <param name="reply"></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");
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<std::string>()) {
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<std::string>();
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::string>(std::to_string(salt));
reply.reply(response);
reply.payload(response);
}
/// <summary>
@ -427,7 +427,7 @@ void RESTAPI::restAPI_PutAuth(const HTTPRequest& request, HTTPReply& reply, cons
/// <param name="request"></param>
/// <param name="reply"></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)) {
return;
@ -437,7 +437,7 @@ void RESTAPI::restAPI_GetVersion(const HTTPRequest& request, HTTPReply& reply, c
setResponseDefaultStatus(response);
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>
@ -446,7 +446,7 @@ void RESTAPI::restAPI_GetVersion(const HTTPRequest& request, HTTPReply& reply, c
/// <param name="request"></param>
/// <param name="reply"></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)) {
return;
@ -563,7 +563,7 @@ void RESTAPI::restAPI_GetStatus(const HTTPRequest& request, HTTPReply& reply, co
response["modem"].set<json::object>(modemInfo);
}
reply.reply(response);
reply.payload(response);
}
/// <summary>
@ -572,7 +572,7 @@ void RESTAPI::restAPI_GetStatus(const HTTPRequest& request, HTTPReply& reply, co
/// <param name="request"></param>
/// <param name="reply"></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)) {
return;
@ -599,7 +599,7 @@ void RESTAPI::restAPI_GetVoiceCh(const HTTPRequest& request, HTTPReply& reply, c
}
response["channels"].set<json::array>(channels);
reply.reply(response);
reply.payload(response);
}
/// <summary>
@ -608,7 +608,7 @@ void RESTAPI::restAPI_GetVoiceCh(const HTTPRequest& request, HTTPReply& reply, c
/// <param name="request"></param>
/// <param name="reply"></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)) {
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<std::string>()) {
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<uint8_t>(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<uint8_t>(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<uint8_t>(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<uint8_t>(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<uint8_t>(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,
/// <param name="request"></param>
/// <param name="reply"></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)) {
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<bool>()) {
g_killed = true;
@ -744,7 +744,7 @@ void RESTAPI::restAPI_PutModemKill(const HTTPRequest& request, HTTPReply& reply,
/// <param name="request"></param>
/// <param name="reply"></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)) {
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<int>()) {
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<int>()) {
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<uint32_t>();
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<int>()) {
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<int>();
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,
/// <param name="request"></param>
/// <param name="reply"></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)) {
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<int>()) {
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<int>()) {
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<uint32_t>();
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<int>()) {
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<int>();
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<int>()) {
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<int>();
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
/// <param name="request"></param>
/// <param name="reply"></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)) {
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
/// <param name="request"></param>
/// <param name="reply"></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)) {
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
/// <param name="request"></param>
/// <param name="reply"></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)) {
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
/// <param name="request"></param>
/// <param name="reply"></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)) {
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
/// <param name="request"></param>
/// <param name="reply"></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)) {
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,
/// <param name="request"></param>
/// <param name="reply"></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)) {
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<bool>(debug);
response["verbose"].set<bool>(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,
/// <param name="request"></param>
/// <param name="reply"></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)) {
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<bool>(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
/// <param name="request"></param>
/// <param name="reply"></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)) {
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);
}
/// <summary>
@ -1242,18 +1242,18 @@ void RESTAPI::restAPI_PutDMRRID(const HTTPRequest& request, HTTPReply& reply, co
/// <param name="request"></param>
/// <param name="reply"></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)) {
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);
}
/// <summary>
@ -1262,18 +1262,18 @@ void RESTAPI::restAPI_GetDMRCCEnable(const HTTPRequest& request, HTTPReply& repl
/// <param name="request"></param>
/// <param name="reply"></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)) {
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
/// <param name="request"></param>
/// <param name="reply"></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)) {
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
/// <param name="request"></param>
/// <param name="reply"></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)) {
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<bool>(debug);
response["verbose"].set<bool>(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,
/// <param name="request"></param>
/// <param name="reply"></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)) {
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<bool>(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
/// <param name="request"></param>
/// <param name="reply"></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)) {
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);
}
/// <summary>
@ -1423,18 +1423,18 @@ void RESTAPI::restAPI_PutP25RID(const HTTPRequest& request, HTTPReply& reply, co
/// <param name="request"></param>
/// <param name="reply"></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)) {
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);
}
/// <summary>
@ -1443,18 +1443,18 @@ void RESTAPI::restAPI_GetP25CCEnable(const HTTPRequest& request, HTTPReply& repl
/// <param name="request"></param>
/// <param name="reply"></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)) {
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
/// <param name="request"></param>
/// <param name="reply"></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)) {
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<bool>(debug);
response["verbose"].set<bool>(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,
/// <param name="request"></param>
/// <param name="reply"></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)) {
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<bool>(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)
}

@ -118,9 +118,8 @@ public:
void close();
private:
typedef network::rest::RequestDispatcher<network::rest::http::HTTPRequest, network::rest::http::HTTPReply> RESTDispatcherType;
typedef network::rest::http::HTTPRequest HTTPRequest;
typedef network::rest::http::HTTPReply HTTPReply;
typedef network::rest::RequestDispatcher<network::rest::http::HTTPPayload, network::rest::http::HTTPPayload> RESTDispatcherType;
typedef network::rest::http::HTTPPayload HTTPPayload;
RESTDispatcherType m_dispatcher;
network::rest::http::HTTPServer<RESTDispatcherType> m_restServer;
@ -152,79 +151,79 @@ private:
/// <summary></summary>
void invalidateHostToken(const std::string host);
/// <summary></summary>
bool validateAuth(const HTTPRequest& request, HTTPReply& reply);
bool validateAuth(const HTTPPayload& request, HTTPPayload& reply);
/// <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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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
*/
/// <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>
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>
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>
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>
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>
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
*/
/// <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>
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>
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>
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>
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>
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
*/
/// <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>
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__

@ -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 <functional>
@ -68,27 +67,27 @@ namespace network
/// <summary></summary>
RequestMatcher<Request, Reply>& get(RequestHandlerType handler) {
m_handlers["GET"] = handler;
m_handlers[HTTP_GET] = handler;
return *this;
}
/// <summary></summary>
RequestMatcher<Request, Reply>& post(RequestHandlerType handler) {
m_handlers["POST"] = handler;
m_handlers[HTTP_POST] = handler;
return *this;
}
/// <summary></summary>
RequestMatcher<Request, Reply>& put(RequestHandlerType handler) {
m_handlers["PUT"] = handler;
m_handlers[HTTP_PUT] = handler;
return *this;
}
/// <summary></summary>
RequestMatcher<Request, Reply>& del(RequestHandlerType handler) {
m_handlers["DELETE"] = handler;
m_handlers[HTTP_DELETE] = handler;
return *this;
}
/// <summary></summary>
RequestMatcher<Request, Reply>& 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<typename Request = http::HTTPRequest, typename Reply = http::HTTPReply>
template<typename Request = http::HTTPPayload, typename Reply = http::HTTPPayload>
class RequestDispatcher {
typedef RequestMatcher<Request, Reply> 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<http::HTTPRequest, http::HTTPReply> DefaultRequestDispatcher;
typedef RequestDispatcher<http::HTTPPayload, http::HTTPPayload> DefaultRequestDispatcher;
} // namespace rest
} // namespace network

@ -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 <array>
#include <memory>
@ -75,11 +74,13 @@ namespace network
public:
/// <summary>Initializes a new instance of the Connection class.</summary>
explicit Connection(asio::ip::tcp::socket socket, ConnectionManagerType& manager, RequestHandlerType& handler,
bool persistent = false) :
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
/// <summary></summary>
Connection& operator=(const Connection&) = delete;
/// <summary>Start the first asynchronous operation for the connection.</summary>
void start() { read(); }
/// <summary>Stop all asynchronous operations associated with the connection.</summary>
@ -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
/// <summary>Perform an asynchronous write operation.</summary>
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<char, 8192> 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

@ -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
// ---------------------------------------------------------------------------
struct HTTPReply;
struct HTTPPayload;
// ---------------------------------------------------------------------------
// Structure Declaration
@ -122,7 +122,7 @@ namespace network
}
private:
friend struct HTTPReply;
friend struct HTTPPayload;
std::vector<Header> m_headers;
};
} // namespace http

@ -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;
// ---------------------------------------------------------------------------
/// <summary>
/// Initializes a new instance of the HTTPRequestLexer class.
/// Initializes a new instance of the HTTPLexer class.
/// </summary>
HTTPRequestLexer::HTTPRequestLexer() :
/// <param name="clientLexer"></param>
HTTPLexer::HTTPLexer(bool clientLexer) :
m_headers(),
m_clientLexer(clientLexer),
m_state(METHOD_START)
{
/* stub */
if (m_clientLexer) {
m_state = HTTP_VERSION_H;
}
}
/// <summary>Reset to initial parser state.</summary>
void HTTPRequestLexer::reset()
void HTTPLexer::reset()
{
m_state = METHOD_START;
if (m_clientLexer) {
m_state = HTTP_VERSION_H;
}
m_headers = std::vector<LexedHeader>();
}
@ -75,7 +82,7 @@ void HTTPRequestLexer::reset()
/// <param name="req"></param>
/// <param name="input"></param>
/// <returns></returns>
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
/// </summary>
/// <param name="c"></param>
/// <returns></returns>
bool HTTPRequestLexer::isChar(int c)
bool HTTPLexer::isChar(int c)
{
return c >= 0 && c <= 127;
}
@ -338,7 +412,7 @@ bool HTTPRequestLexer::isChar(int c)
/// </summary>
/// <param name="c"></param>
/// <returns></returns>
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)
/// </summary>
/// <param name="c"></param>
/// <returns></returns>
bool HTTPRequestLexer::isSpecial(int c)
bool HTTPLexer::isSpecial(int c)
{
switch (c)
{
@ -367,7 +441,7 @@ bool HTTPRequestLexer::isSpecial(int c)
/// </summary>
/// <param name="c"></param>
/// <returns></returns>
bool HTTPRequestLexer::isDigit(int c)
bool HTTPLexer::isDigit(int c)
{
return c >= '0' && c <= '9';
}

@ -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 <tuple>
#include <vector>
@ -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 };
/// <summary>Initializes a new instance of the HTTPRequestLexer class.</summary>
HTTPRequestLexer();
/// <summary>Initializes a new instance of the HTTPLexer class.</summary>
HTTPLexer(bool clientLexer);
/// <summary>Reset to initial parser state.</summary>
void reset();
@ -74,10 +76,10 @@ namespace network
/// required. The InputIterator return value indicates how much of the input
/// has been consumed.</summary>
template <typename InputIterator>
std::tuple<ResultType, InputIterator> parse(HTTPRequest& req, InputIterator begin, InputIterator end)
std::tuple<ResultType, InputIterator> 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:
/// <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>
static bool isChar(int c);
@ -107,6 +109,8 @@ namespace network
};
std::vector<LexedHeader> 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__

@ -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 {
"</html>";
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.
/// </summary>
std::vector<asio::const_buffer> HTTPReply::toBuffers()
std::vector<asio::const_buffer> HTTPPayload::toBuffers()
{
std::vector<asio::const_buffer> 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<asio::const_buffer> HTTPReply::toBuffers()
/// </summary>
/// <param name="obj"></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);
std::string json = v.serialize();
reply(json, s, "application/json");
payload(json, s, "application/json");
}
/// <summary>
@ -332,7 +341,7 @@ void HTTPReply::reply(json::object obj, HTTPReply::StatusType s)
/// <param name="c"></param>
/// <param name="s"></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;
status = s;
@ -344,16 +353,34 @@ void HTTPReply::reply(std::string c, HTTPReply::StatusType s, std::string conten
// ---------------------------------------------------------------------------
/// <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>
/// <param name="status"></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;
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
///
/// </summary>
/// <param name="contentType"></param>
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());
}

@ -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
{
/// <summary>
/// HTTP Status/Response Codes
@ -85,18 +98,28 @@ namespace network
HTTPHeaders headers;
std::string content;
/// <summary>Convert the reply into a vector of buffers. The buffers do not own the
/// underlying memory blocks, therefore the reply object must remain valid and
std::string method;
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>
std::vector<asio::const_buffer> toBuffers();
/// <summary>Prepares reply for transmission by finalizing status and content type.</summary>
void reply(json::object obj, StatusType status = OK);
/// <summary>Prepares reply for transmission by finalizing status and content type.</summary>
void reply(std::string content, StatusType status = OK, std::string contentType = "text/html");
/// <summary>Prepares payload for transmission by finalizing status and content type.</summary>
void payload(json::object obj, StatusType status = OK);
/// <summary>Prepares payload for transmission by finalizing status and content type.</summary>
void payload(std::string content, StatusType status = OK, std::string contentType = "text/html");
/// <summary>Get a stock reply.</summary>
static HTTPReply stockReply(StatusType status, std::string contentType = "text/html");
/// <summary>Get a request payload.</summary>
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:
/// <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 "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) :
/// <summary>
/// Handle a request and produce a reply.
/// </summary>
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)

@ -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;
/// <summary>Handle a request and produce a reply.</summary>
void handleRequest(const HTTPRequest& req, HTTPReply& reply);
void handleRequest(const HTTPPayload& req, HTTPPayload& reply);
private:
/// <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/ConnectionManager.h"
#include "network/rest/http/HTTPRequestHandler.h"
#include "network/rest/http/HTTPReply.h"
#include "network/rest/http/HTTPRequest.h"
#include <asio.hpp>

Loading…
Cancel
Save

Powered by TurnKey Linux.