1132 lines
47 KiB
C++
1132 lines
47 KiB
C++
/*++
|
|
|
|
Program name:
|
|
|
|
dm
|
|
|
|
Module Name:
|
|
|
|
WebService.cpp
|
|
|
|
Notices:
|
|
|
|
WebService - Deal Module Web Service
|
|
|
|
Author:
|
|
|
|
Copyright (c) Prepodobny Alen
|
|
|
|
mailto: alienufo@inbox.ru
|
|
mailto: ufocomp@gmail.com
|
|
|
|
--*/
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
#include "Core.hpp"
|
|
#include "WebService.hpp"
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
#define BPS_PGP_HASH "SHA512"
|
|
#define BM_PREFIX "BM-"
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
#define SYSTEM_PROVIDER_NAME "system"
|
|
#define SERVICE_APPLICATION_NAME "service"
|
|
#define CONFIG_SECTION_NAME "module"
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
extern "C++" {
|
|
|
|
namespace Apostol {
|
|
|
|
namespace Module {
|
|
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
|
|
//-- CWebService -----------------------------------------------------------------------------------------------
|
|
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
|
|
CWebService::CWebService(CModuleProcess *AProcess, const CString& ModuleName, const CString& SectionName):
|
|
CCustomModule(AProcess, ModuleName, SectionName) {
|
|
|
|
m_NexServerTime = 0;
|
|
CWebService::InitMethods();
|
|
|
|
InitServerList();
|
|
}
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
|
|
void CWebService::InitMethods() {
|
|
CCustomModule::InitMethods();
|
|
}
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
|
|
void CWebService::InitServerList() {
|
|
m_DefaultServer.Value().PGP().Add(CKeyContext(CString("PUBLIC"), CString()));
|
|
m_DefaultServer.Value().PGP().Add(CKeyContext(m_DefaultServer.Name(), CString()));
|
|
|
|
if (m_Servers.Count() == 0) {
|
|
#ifdef _DEBUG
|
|
int index = m_Servers.AddPair(BPS_BM_DEBUG_ADDRESS, CContext(BPS_BM_DEBUG_ADDRESS, CLocation(BPS_SERVER_URL)));
|
|
auto &Keys = m_Servers[index].Value().PGP();
|
|
Keys.Add(CKeyContext(CString("PUBLIC"), CString()));
|
|
Keys.Add(CKeyContext(CString(BPS_BM_DEBUG_ADDRESS), CString()));
|
|
#else
|
|
m_Servers.Add(m_DefaultServer);
|
|
#endif
|
|
}
|
|
}
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
|
|
bool CWebService::DoProxyExecute(CTCPConnection *AConnection) {
|
|
|
|
auto pConnection = dynamic_cast<CHTTPClientConnection *> (AConnection);
|
|
auto pProxy = dynamic_cast<CHTTPProxy *> (pConnection->Client());
|
|
|
|
if (Assigned(pProxy) && (pProxy->Connection() != nullptr)) {
|
|
auto pProxyConnection = pProxy->Connection();
|
|
auto pServerRequest = pProxyConnection->Request();
|
|
auto pServerReply = pProxyConnection->Reply();
|
|
auto pProxyReply = pConnection->Reply();
|
|
|
|
const auto &caFormat = pServerRequest->Params["payload"];
|
|
if (!caFormat.IsEmpty()) {
|
|
|
|
if (caFormat == "html") {
|
|
pServerReply->ContentType = CHTTPReply::html;
|
|
} else if (caFormat == "json") {
|
|
pServerReply->ContentType = CHTTPReply::json;
|
|
} else if (caFormat == "xml") {
|
|
pServerReply->ContentType = CHTTPReply::xml;
|
|
} else {
|
|
pServerReply->ContentType = CHTTPReply::text;
|
|
}
|
|
|
|
if (pProxyReply->Status == CHTTPReply::ok) {
|
|
if (!pProxyReply->Content.IsEmpty()) {
|
|
const CJSON json(pProxyReply->Content);
|
|
pServerReply->Content = base64_decode(json["payload"].AsString());
|
|
}
|
|
pProxyConnection->SendReply(pProxyReply->Status, nullptr, true);
|
|
} else {
|
|
pProxyConnection->SendStockReply(pProxyReply->Status, true);
|
|
}
|
|
} else {
|
|
if (pProxyReply->Status == CHTTPReply::ok) {
|
|
pServerReply->Content = pProxyReply->Content;
|
|
pProxyConnection->SendReply(pProxyReply->Status, nullptr, true);
|
|
} else {
|
|
pProxyConnection->SendStockReply(pProxyReply->Status, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
pConnection->CloseConnection(true);
|
|
|
|
return true;
|
|
}
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
|
|
void CWebService::DoProxyException(CTCPConnection *AConnection, const Delphi::Exception::Exception &E) {
|
|
auto pConnection = dynamic_cast<CHTTPClientConnection*> (AConnection);
|
|
auto pProxy = dynamic_cast<CHTTPProxy*> (pConnection->Client());
|
|
|
|
if (Assigned(pProxy) && (pProxy->Connection() != nullptr)) {
|
|
auto pProxyConnection = pProxy->Connection();
|
|
auto pServerRequest = pProxyConnection->Request();
|
|
auto pServerReply = pProxyConnection->Reply();
|
|
|
|
const auto &Format = pServerRequest->Params["format"];
|
|
if (Format == "html") {
|
|
pServerReply->ContentType = CHTTPReply::html;
|
|
}
|
|
|
|
try {
|
|
pProxyConnection->SendStockReply(CHTTPReply::bad_gateway, true);
|
|
} catch (...) {
|
|
|
|
}
|
|
}
|
|
|
|
Log()->Error(APP_LOG_EMERG, 0, "[%s:%d] %s", pProxy->Host().c_str(), pProxy->Port(), E.what());
|
|
}
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
|
|
void CWebService::DoEventHandlerException(CPollEventHandler *AHandler, const Delphi::Exception::Exception &E) {
|
|
auto pConnection = dynamic_cast<CHTTPClientConnection*> (AHandler->Binding());
|
|
auto pProxy = dynamic_cast<CHTTPProxy*> (pConnection->Client());
|
|
|
|
if (Assigned(pProxy) && (pProxy->Connection() != nullptr)) {
|
|
auto pProxyConnection = pProxy->Connection();
|
|
auto pReply = pProxyConnection->Reply();
|
|
ExceptionToJson(0, E, pReply->Content);
|
|
pProxyConnection->SendReply(CHTTPReply::internal_server_error, nullptr, true);
|
|
}
|
|
|
|
Log()->Error(APP_LOG_EMERG, 0, E.what());
|
|
}
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
|
|
void CWebService::DoProxyConnected(CObject *Sender) {
|
|
auto pConnection = dynamic_cast<CHTTPClientConnection*> (Sender);
|
|
if (pConnection != nullptr) {
|
|
Log()->Message(_T("[%s:%d] Client connected."), pConnection->Socket()->Binding()->PeerIP(),
|
|
pConnection->Socket()->Binding()->PeerPort());
|
|
}
|
|
}
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
|
|
void CWebService::DoProxyDisconnected(CObject *Sender) {
|
|
auto pConnection = dynamic_cast<CHTTPClientConnection*> (Sender);
|
|
if (pConnection != nullptr) {
|
|
Log()->Message(_T("[%s:%d] Client disconnected."), pConnection->Socket()->Binding()->PeerIP(),
|
|
pConnection->Socket()->Binding()->PeerPort());
|
|
}
|
|
}
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
|
|
CHTTPProxy *CWebService::GetProxy(CHTTPServerConnection *AConnection) {
|
|
auto pProxy = m_ProxyManager.Add(AConnection);
|
|
#if defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE >= 9)
|
|
pProxy->OnVerbose([this](auto && Sender, auto && AConnection, auto && AFormat, auto && args) { DoVerbose(Sender, AConnection, AFormat, args); });
|
|
|
|
pProxy->OnExecute([this](auto && AConnection) { return DoProxyExecute(AConnection); });
|
|
|
|
pProxy->OnException([this](auto && AConnection, auto && E) { DoProxyException(AConnection, E); });
|
|
pProxy->OnEventHandlerException([this](auto && AHandler, auto && E) { DoEventHandlerException(AHandler, E); });
|
|
|
|
pProxy->OnConnected([this](auto && Sender) { DoProxyConnected(Sender); });
|
|
pProxy->OnDisconnected([this](auto && Sender) { DoProxyDisconnected(Sender); });
|
|
#else
|
|
pProxy->OnVerbose(std::bind(&CWebService::DoVerbose, this, _1, _2, _3, _4));
|
|
|
|
pProxy->OnExecute(std::bind(&CWebService::DoProxyExecute, this, _1));
|
|
|
|
pProxy->OnException(std::bind(&CWebService::DoProxyException, this, _1, _2));
|
|
pProxy->OnEventHandlerException(std::bind(&CWebService::DoEventHandlerException, this, _1, _2));
|
|
|
|
pProxy->OnConnected(std::bind(&CWebService::DoProxyConnected, this, _1));
|
|
pProxy->OnDisconnected(std::bind(&CWebService::DoProxyDisconnected, this, _1));
|
|
#endif
|
|
return pProxy;
|
|
}
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
|
|
void CWebService::DoAccount(CHTTPServerConnection *AConnection, const CString &Method, const CString &URI) {
|
|
|
|
auto pProxy = GetProxy(AConnection);
|
|
auto pServerRequest = AConnection->Request();
|
|
auto pProxyRequest = pProxy->Request();
|
|
|
|
const auto &caModuleAddress = m_Module["address"];
|
|
const auto &caHost = pServerRequest->Headers["host"];
|
|
const auto &caOrigin = pServerRequest->Headers["origin"];
|
|
const auto &caUserAddress = pServerRequest->Params["address"];
|
|
|
|
const auto &pgpValue = pServerRequest->Params["pgp"];
|
|
const auto &caServerParam = pServerRequest->Params["server"];
|
|
|
|
const auto &caServer = caServerParam.IsEmpty() ? m_CurrentServer.URL().Origin() : caServerParam;
|
|
|
|
CLocation Location(caServer);
|
|
|
|
pProxy->Host() = Location.hostname;
|
|
pProxy->Port(Location.port == 0 ? BPS_SERVER_PORT : Location.port);
|
|
|
|
CStringList caClearText;
|
|
CString sPayload;
|
|
|
|
if (!pServerRequest->Content.IsEmpty()) {
|
|
|
|
const auto &ContentType = pServerRequest->Headers.Values(_T("content-type"));
|
|
|
|
if (ContentType.Find("application/x-www-form-urlencoded") == 0) {
|
|
|
|
const CStringList &FormData = pServerRequest->FormData;
|
|
|
|
const auto &formDate = FormData["date"];
|
|
const auto &formAddress = FormData["address"];
|
|
const auto &formBitmessage = FormData["bitmessage"];
|
|
const auto &formKey = FormData["key"];
|
|
const auto &formPGP = FormData["pgp"];
|
|
const auto &formURL = FormData["url"];
|
|
const auto &formFlags = FormData["flags"];
|
|
const auto &formSign = FormData["sign"];
|
|
|
|
if (!formDate.IsEmpty()) {
|
|
caClearText << formDate;
|
|
}
|
|
|
|
if (!formAddress.IsEmpty()) {
|
|
caClearText << formAddress;
|
|
}
|
|
|
|
if (!formFlags.IsEmpty()) {
|
|
caClearText << formFlags;
|
|
}
|
|
|
|
if (!formBitmessage.IsEmpty()) {
|
|
caClearText << formBitmessage;
|
|
}
|
|
|
|
if (!formKey.IsEmpty()) {
|
|
caClearText << formKey;
|
|
}
|
|
|
|
if (!formPGP.IsEmpty()) {
|
|
caClearText << formPGP;
|
|
}
|
|
|
|
if (!formURL.IsEmpty()) {
|
|
caClearText << formURL;
|
|
}
|
|
|
|
if (!formSign.IsEmpty()) {
|
|
caClearText << formSign;
|
|
}
|
|
|
|
} else if (ContentType.Find("multipart/form-data") == 0) {
|
|
|
|
CFormData FormData;
|
|
CHTTPRequestParser::ParseFormData(pServerRequest, FormData);
|
|
|
|
const auto &formDate = FormData.Data("date");
|
|
const auto &formAddress = FormData.Data("address");
|
|
const auto &formBitmessage = FormData.Data("bitmessage");
|
|
const auto &formKey = FormData.Data("key");
|
|
const auto &formPGP = FormData.Data("pgp");
|
|
const auto &formURL = FormData.Data("url");
|
|
const auto &formFlags = FormData.Data("flags");
|
|
const auto &formSign = FormData.Data("sign");
|
|
|
|
if (!formDate.IsEmpty()) {
|
|
caClearText << formDate;
|
|
}
|
|
|
|
if (!formAddress.IsEmpty()) {
|
|
caClearText << formAddress;
|
|
}
|
|
|
|
if (!formFlags.IsEmpty()) {
|
|
caClearText << formFlags;
|
|
}
|
|
|
|
if (!formBitmessage.IsEmpty()) {
|
|
caClearText << formBitmessage;
|
|
}
|
|
|
|
if (!formKey.IsEmpty()) {
|
|
caClearText << formKey;
|
|
}
|
|
|
|
if (!formPGP.IsEmpty()) {
|
|
caClearText << formPGP;
|
|
}
|
|
|
|
if (!formURL.IsEmpty()) {
|
|
caClearText << formURL;
|
|
}
|
|
|
|
if (!formSign.IsEmpty()) {
|
|
caClearText << formSign;
|
|
}
|
|
|
|
} else if (ContentType.Find("application/json") == 0) {
|
|
|
|
const CJSON contextJson(pServerRequest->Content);
|
|
|
|
const auto &jsonDate = contextJson["date"].AsString();
|
|
const auto &jsonAddress = contextJson["address"].AsString();
|
|
const auto &jsonBitmessage = contextJson["bitmessage"].AsString();
|
|
const auto &jsonKey = contextJson["key"].AsString();
|
|
const auto &jsonPGP = contextJson["pgp"].AsString();
|
|
const auto &jsonFlags = contextJson["flags"].AsString();
|
|
const auto &jsonSign = contextJson["sign"].AsString();
|
|
|
|
if (!jsonDate.IsEmpty()) {
|
|
caClearText << jsonDate;
|
|
}
|
|
|
|
if (!jsonAddress.IsEmpty()) {
|
|
caClearText << jsonAddress;
|
|
}
|
|
|
|
if (!jsonFlags.IsEmpty()) {
|
|
caClearText << jsonFlags;
|
|
}
|
|
|
|
if (!jsonBitmessage.IsEmpty()) {
|
|
caClearText << jsonBitmessage;
|
|
}
|
|
|
|
if (!jsonKey.IsEmpty()) {
|
|
caClearText << jsonKey;
|
|
}
|
|
|
|
if (!jsonPGP.IsEmpty()) {
|
|
caClearText << jsonPGP;
|
|
}
|
|
|
|
const CJSONValue &jsonURL = contextJson["url"];
|
|
if (jsonURL.IsArray()) {
|
|
const CJSONArray &arrayURL = jsonURL.Array();
|
|
for (int i = 0; i < arrayURL.Count(); i++) {
|
|
caClearText << arrayURL[i].AsString();
|
|
}
|
|
}
|
|
|
|
if (!jsonSign.IsEmpty()) {
|
|
caClearText << jsonSign;
|
|
}
|
|
|
|
} else {
|
|
caClearText = pServerRequest->Content;
|
|
}
|
|
|
|
if (pgpValue == "off" || pgpValue == "false") {
|
|
sPayload = caClearText.Text();
|
|
} else {
|
|
Apostol::PGP::CleartextSignature(
|
|
m_pgpModuleKey,
|
|
m_pgpPassphrase,
|
|
BPS_PGP_HASH,
|
|
caClearText.Text(),
|
|
sPayload);
|
|
}
|
|
}
|
|
|
|
//DebugMessage("[DoAccount] Server request:\n%s\n", pServerRequest->Content.c_str());
|
|
//DebugMessage("[DoAccount] sPayload:\n%s\n", sPayload.c_str());
|
|
|
|
CJSON Json(jvtObject);
|
|
CJSONValue Module(jvtObject);
|
|
|
|
Module.Object().AddPair("address", caModuleAddress);
|
|
|
|
Json.Object().AddPair("id", GetUID(16).Lower());
|
|
Json.Object().AddPair("host", caHost);
|
|
|
|
if (!caOrigin.IsEmpty()) {
|
|
Json.Object().AddPair("origin", caOrigin);
|
|
}
|
|
|
|
Json.Object().AddPair("module", Module);
|
|
Json.Object().AddPair("address", caUserAddress.IsEmpty() ? caModuleAddress : caUserAddress);
|
|
|
|
if (!sPayload.IsEmpty())
|
|
Json.Object().AddPair("payload", base64_encode(sPayload));
|
|
|
|
pProxyRequest->Clear();
|
|
|
|
pProxyRequest->Location = pServerRequest->Location;
|
|
pProxyRequest->CloseConnection = false;
|
|
pProxyRequest->ContentType = CHTTPRequest::json;
|
|
pProxyRequest->Content << Json;
|
|
|
|
CHTTPRequest::Prepare(pProxyRequest, Method.c_str(), URI.c_str());
|
|
|
|
pProxyRequest->AddHeader("Authorization", "Bearer " + m_CurrentServer.Tokens()["access_token"]);
|
|
|
|
if (!caModuleAddress.IsEmpty())
|
|
pProxyRequest->AddHeader("Module-Address", caModuleAddress);
|
|
|
|
if (!caOrigin.IsEmpty())
|
|
pProxyRequest->AddHeader("Origin", caOrigin);
|
|
|
|
AConnection->CloseConnection(false);
|
|
pProxy->Active(true);
|
|
}
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
|
|
void CWebService::DoDeal(CHTTPServerConnection *AConnection, const CString &Method, const CString &URI, const CString &Action) {
|
|
|
|
auto pProxy = GetProxy(AConnection);
|
|
auto pServerRequest = AConnection->Request();
|
|
auto pProxyRequest = pProxy->Request();
|
|
|
|
const auto &caModuleAddress = m_Module["address"];
|
|
const auto &caModuleFee = m_Module["fee"];
|
|
|
|
const auto checkFee = CheckFee(caModuleFee);
|
|
if (checkFee == -1)
|
|
throw ExceptionFrm("Invalid module fee value: %s", caModuleFee.c_str());
|
|
|
|
const auto &caHost = pServerRequest->Headers["host"];
|
|
const auto &caOrigin = pServerRequest->Headers["origin"];
|
|
|
|
const auto &address = pServerRequest->Params["address"];
|
|
const auto &code = pServerRequest->Params["code"];
|
|
|
|
const auto &caUserAddress = address.length() == 40 ? CString() : address;
|
|
const auto &caDealCode = !code.empty() ? code : address.length() == 40 ? address : CString();
|
|
|
|
const auto &pgpValue = pServerRequest->Params["pgp"];
|
|
const auto &caServerParam = pServerRequest->Params["server"];
|
|
|
|
const auto &caServer = caServerParam.IsEmpty() ? m_CurrentServer.URL().Origin() : caServerParam;
|
|
|
|
CLocation Location(caServer);
|
|
|
|
pProxy->Host() = Location.hostname;
|
|
pProxy->Port(Location.port == 0 ? BPS_SERVER_PORT : Location.port);
|
|
|
|
YAML::Node Node;
|
|
|
|
CString sPayload;
|
|
|
|
if (!pServerRequest->Content.IsEmpty()) {
|
|
|
|
const auto &ContentType = pServerRequest->Headers.Values(_T("content-type"));
|
|
|
|
if (ContentType.Find("application/x-www-form-urlencoded") == 0) {
|
|
|
|
const CStringList &FormData = pServerRequest->FormData;
|
|
|
|
const auto &formType = FormData["type"];
|
|
const auto &formAt = FormData["at"];
|
|
const auto &formDate = FormData["date"];
|
|
const auto &formSellerAddress = FormData["seller_address"];
|
|
const auto &formSellerRating = FormData["seller_rating"];
|
|
const auto &formSellerSignature = FormData["seller_signature"];
|
|
const auto &formCustomerAddress = FormData["customer_address"];
|
|
const auto &formCustomerRating = FormData["customer_rating"];
|
|
const auto &formCustomerSignature = FormData["customer_signature"];
|
|
const auto &formPaymentAddress = FormData["payment_address"];
|
|
const auto &formPaymentUntil = FormData["payment_until"];
|
|
const auto &formPaymentSum = FormData["payment_sum"];
|
|
const auto &formFeedbackLeaveBefore = FormData["feedback_leave_before"];
|
|
const auto &formFeedbackStatus = FormData["feedback_status"];
|
|
const auto &formFeedbackComments = FormData["feedback_comments"];
|
|
|
|
CheckKeyForNull("order", Action.c_str());
|
|
CheckKeyForNull("type", formType.c_str());
|
|
CheckKeyForNull("at", formAt.c_str());
|
|
CheckKeyForNull("date", formDate.c_str());
|
|
CheckKeyForNull("seller_address", formSellerAddress.c_str());
|
|
CheckKeyForNull("customer_address", formCustomerAddress.c_str());
|
|
CheckKeyForNull("payment_sum", formPaymentSum.c_str());
|
|
|
|
if (Action == "cancel") {
|
|
CheckKeyForNull("seller_signature", formSellerSignature.c_str());
|
|
}
|
|
|
|
if (Action == "feedback") {
|
|
CheckKeyForNull("customer_signature", formCustomerSignature.c_str());
|
|
}
|
|
|
|
YAML::Node Deal = Node["deal"];
|
|
|
|
Deal["order"] = Action.c_str();
|
|
Deal["type"] = formType.c_str();
|
|
|
|
Deal["at"] = formAt.c_str();
|
|
Deal["date"] = formDate.c_str();
|
|
|
|
YAML::Node Seller = Deal["seller"];
|
|
|
|
Seller["address"] = formSellerAddress.c_str();
|
|
|
|
if (!formSellerRating.IsEmpty())
|
|
Seller["rating"] = formSellerRating.c_str();
|
|
|
|
if (!formSellerSignature.IsEmpty())
|
|
Seller["signature"] = formSellerSignature.c_str();
|
|
|
|
YAML::Node Customer = Deal["customer"];
|
|
|
|
Customer["address"] = formCustomerAddress.c_str();
|
|
|
|
if (!formCustomerRating.IsEmpty())
|
|
Customer["rating"] = formCustomerRating.c_str();
|
|
|
|
if (!formCustomerSignature.IsEmpty())
|
|
Customer["signature"] = formCustomerSignature.c_str();
|
|
|
|
YAML::Node Payment = Deal["payment"];
|
|
|
|
if (!formPaymentAddress.IsEmpty())
|
|
Payment["address"] = formPaymentAddress.c_str();
|
|
|
|
if (!formPaymentUntil.IsEmpty())
|
|
Payment["until"] = formPaymentUntil.c_str();
|
|
|
|
Payment["sum"] = formPaymentSum.c_str();
|
|
|
|
if (!formFeedbackLeaveBefore.IsEmpty()) {
|
|
YAML::Node Feedback = Deal["feedback"];
|
|
|
|
Feedback["leave-before"] = formFeedbackLeaveBefore.c_str();
|
|
|
|
if (!formFeedbackStatus.IsEmpty())
|
|
Feedback["status"] = formFeedbackStatus.c_str();
|
|
|
|
if (!formFeedbackComments.IsEmpty())
|
|
Feedback["comments"] = formFeedbackComments.c_str();
|
|
}
|
|
|
|
} else if (ContentType.Find("multipart/form-data") == 0) {
|
|
|
|
CFormData FormData;
|
|
CHTTPRequestParser::ParseFormData(pServerRequest, FormData);
|
|
|
|
const auto &formType = FormData.Data("type");
|
|
const auto &formAt = FormData.Data("at");
|
|
const auto &formDate = FormData.Data("date");
|
|
const auto &formSellerAddress = FormData.Data("seller_address");
|
|
const auto &formSellerRating = FormData.Data("seller_rating");
|
|
const auto &formSellerSignature = FormData.Data("seller_signature");
|
|
const auto &formCustomerAddress = FormData.Data("customer_address");
|
|
const auto &formCustomerRating = FormData.Data("customer_rating");
|
|
const auto &formCustomerSignature = FormData.Data("customer_signature");
|
|
const auto &formPaymentAddress = FormData.Data("payment_address");
|
|
const auto &formPaymentUntil = FormData.Data("payment_until");
|
|
const auto &formPaymentSum = FormData.Data("payment_sum");
|
|
const auto &formFeedbackLeaveBefore = FormData.Data("feedback_leave_before");
|
|
const auto &formFeedbackStatus = FormData.Data("feedback_status");
|
|
const auto &formFeedbackComments = FormData.Data("feedback_comments");
|
|
|
|
CheckKeyForNull("order", Action.c_str());
|
|
CheckKeyForNull("type", formType.c_str());
|
|
CheckKeyForNull("at", formAt.c_str());
|
|
CheckKeyForNull("date", formDate.c_str());
|
|
CheckKeyForNull("seller_address", formSellerAddress.c_str());
|
|
CheckKeyForNull("customer_address", formCustomerAddress.c_str());
|
|
CheckKeyForNull("payment_sum", formPaymentSum.c_str());
|
|
|
|
if (Action == "cancel") {
|
|
CheckKeyForNull("seller_signature", formSellerSignature.c_str());
|
|
}
|
|
|
|
if (Action == "feedback") {
|
|
CheckKeyForNull("customer_signature", formCustomerSignature.c_str());
|
|
}
|
|
|
|
YAML::Node Deal = Node["deal"];
|
|
|
|
Deal["order"] = Action.c_str();
|
|
Deal["type"] = formType.c_str();
|
|
|
|
Deal["at"] = formAt.c_str();
|
|
Deal["date"] = formDate.c_str();
|
|
|
|
YAML::Node Seller = Deal["seller"];
|
|
|
|
Seller["address"] = formSellerAddress.c_str();
|
|
|
|
if (!formSellerRating.IsEmpty())
|
|
Seller["rating"] = formSellerRating.c_str();
|
|
|
|
if (!formSellerSignature.IsEmpty())
|
|
Seller["signature"] = formSellerSignature.c_str();
|
|
|
|
YAML::Node Customer = Deal["customer"];
|
|
|
|
Customer["address"] = formCustomerAddress.c_str();
|
|
|
|
if (!formSellerRating.IsEmpty())
|
|
Customer["rating"] = formCustomerRating.c_str();
|
|
|
|
if (!formCustomerSignature.IsEmpty())
|
|
Customer["signature"] = formCustomerSignature.c_str();
|
|
|
|
YAML::Node Payment = Deal["payment"];
|
|
|
|
if (!formPaymentAddress.IsEmpty())
|
|
Payment["address"] = formPaymentAddress.c_str();
|
|
|
|
if (!formPaymentUntil.IsEmpty())
|
|
Payment["until"] = formPaymentUntil.c_str();
|
|
|
|
Payment["sum"] = formPaymentSum.c_str();
|
|
|
|
if (!formFeedbackLeaveBefore.IsEmpty()) {
|
|
YAML::Node Feedback = Deal["feedback"];
|
|
|
|
Feedback["leave-before"] = formFeedbackLeaveBefore.c_str();
|
|
|
|
if (!formFeedbackStatus.IsEmpty())
|
|
Feedback["status"] = formFeedbackStatus.c_str();
|
|
|
|
if (!formFeedbackComments.IsEmpty())
|
|
Feedback["comments"] = formFeedbackComments.c_str();
|
|
}
|
|
|
|
} else if (ContentType.Find("application/json") == 0) {
|
|
|
|
const CJSON jsonData(pServerRequest->Content);
|
|
|
|
const auto &formOrder = jsonData["order"].AsString().Lower();
|
|
const auto &formType = jsonData["type"].AsString();
|
|
|
|
const auto &formAt = jsonData["at"].AsString();
|
|
const auto &formDate = jsonData["date"].AsString();
|
|
|
|
const CJSONValue &jsonSeller = jsonData["seller"];
|
|
|
|
const auto &formSellerAddress = jsonSeller["address"].AsString();
|
|
const auto &formSellerRating = jsonSeller["rating"].AsString();
|
|
const auto &formSellerSignature = jsonSeller["signature"].AsString();
|
|
|
|
const CJSONValue &jsonCustomer = jsonData["customer"];
|
|
|
|
const auto &formCustomerAddress = jsonCustomer["address"].AsString();
|
|
const auto &formCustomerRating = jsonCustomer["rating"].AsString();
|
|
const auto &formCustomerSignature = jsonCustomer["signature"].AsString();
|
|
|
|
const CJSONValue &jsonPayment = jsonData["payment"];
|
|
|
|
const auto &formPaymentAddress = jsonPayment["address"].AsString();
|
|
const auto &formPaymentUntil = jsonPayment["until"].AsString();
|
|
const auto &formPaymentSum = jsonPayment["sum"].AsString();
|
|
|
|
const CJSONValue &jsonFeedback = jsonData["feedback"];
|
|
|
|
const auto &formFeedbackLeaveBefore = jsonFeedback["leave-before"].AsString();
|
|
const auto &formFeedbackStatus = jsonFeedback["status"].AsString();
|
|
const auto &formFeedbackComments = jsonFeedback["comments"].AsString();
|
|
|
|
const auto &action = Action.IsEmpty() ? formOrder : Action;
|
|
|
|
CheckKeyForNull("order", action.c_str());
|
|
CheckKeyForNull("type", formType.c_str());
|
|
CheckKeyForNull("at", formAt.c_str());
|
|
CheckKeyForNull("date", formDate.c_str());
|
|
CheckKeyForNull("seller.address", formSellerAddress.c_str());
|
|
CheckKeyForNull("customer.address", formCustomerAddress.c_str());
|
|
CheckKeyForNull("payment.sum", formPaymentSum.c_str());
|
|
|
|
if (action == "cancel") {
|
|
CheckKeyForNull("seller.signature", formSellerSignature.c_str());
|
|
}
|
|
|
|
if (action == "feedback") {
|
|
CheckKeyForNull("customer.signature", formCustomerSignature.c_str());
|
|
}
|
|
|
|
YAML::Node Deal = Node["deal"];
|
|
|
|
Deal["order"] = action.c_str();
|
|
Deal["type"] = formType.c_str();
|
|
|
|
Deal["at"] = formAt.c_str();
|
|
Deal["date"] = formDate.c_str();
|
|
|
|
YAML::Node Seller = Deal["seller"];
|
|
|
|
Seller["address"] = formSellerAddress.c_str();
|
|
|
|
if (!formSellerRating.IsEmpty())
|
|
Seller["rating"] = formSellerRating.c_str();
|
|
|
|
YAML::Node Customer = Deal["customer"];
|
|
|
|
Customer["address"] = formCustomerAddress.c_str();
|
|
|
|
if (!formSellerRating.IsEmpty())
|
|
Customer["rating"] = formCustomerRating.c_str();
|
|
|
|
YAML::Node Payment = Deal["payment"];
|
|
|
|
if (!formPaymentAddress.IsEmpty())
|
|
Payment["address"] = formPaymentAddress.c_str();
|
|
|
|
if (!formPaymentUntil.IsEmpty())
|
|
Payment["until"] = formPaymentUntil.c_str();
|
|
|
|
Payment["sum"] = formPaymentSum.c_str();
|
|
|
|
if (!formFeedbackLeaveBefore.IsEmpty()) {
|
|
YAML::Node Feedback = Deal["feedback"];
|
|
|
|
Feedback["leave-before"] = formFeedbackLeaveBefore.c_str();
|
|
|
|
if (!formFeedbackStatus.IsEmpty())
|
|
Feedback["status"] = formFeedbackStatus.c_str();
|
|
|
|
if (!formFeedbackComments.IsEmpty())
|
|
Feedback["comments"] = formFeedbackComments.c_str();
|
|
}
|
|
|
|
} else {
|
|
Node = YAML::Load(pServerRequest->Content.c_str());
|
|
}
|
|
|
|
const auto &BTCKeys = m_CurrentServer.BTCKeys();
|
|
|
|
if (BTCKeys.Count() < 2)
|
|
throw ExceptionFrm("Bitcoin keys cannot be empty.");
|
|
|
|
for (int i = 0; i < BTCKeys.Count(); ++i) {
|
|
if (BTCKeys[i].IsEmpty())
|
|
throw ExceptionFrm("Bitcoin KEY%d cannot be empty.", i);
|
|
}
|
|
|
|
CDeal Deal(Node);
|
|
|
|
auto& Data = Deal.Data();
|
|
|
|
if (Data.Order == doCreate) {
|
|
Data.Payment.Address = Deal.GetPaymentHD(BTCKeys.Names(0), BTCKeys.Names(1),
|
|
Deal.Data().Transaction.Key, BitcoinConfig.version_hd, BitcoinConfig.version_script);
|
|
|
|
Node["deal"]["date"] = Data.Date.c_str();
|
|
|
|
YAML::Node Payment = Node["deal"]["payment"];
|
|
Payment.remove("sum");
|
|
|
|
Payment["address"] = Data.Payment.Address.c_str();
|
|
Payment["until"] = Data.Payment.Until.c_str();
|
|
Payment["sum"] = Data.Payment.Sum.c_str();
|
|
|
|
Node["deal"]["feedback"]["leave-before"] = Data.FeedBack.LeaveBefore.c_str();
|
|
}
|
|
|
|
CheckDeal(Deal);
|
|
|
|
const CString caClearText(YAML::Dump(Node));
|
|
|
|
if (pgpValue == "off" || pgpValue == "false") {
|
|
sPayload = caClearText;
|
|
} else {
|
|
Apostol::PGP::CleartextSignature(
|
|
m_pgpModuleKey,
|
|
m_pgpPassphrase,
|
|
BPS_PGP_HASH,
|
|
caClearText,
|
|
sPayload);
|
|
}
|
|
}
|
|
|
|
CJSON Json(jvtObject);
|
|
CJSONValue Module(jvtObject);
|
|
|
|
Module.Object().AddPair("address", caModuleAddress);
|
|
Module.Object().AddPair("fee", caModuleFee);
|
|
|
|
Json.Object().AddPair("id", GetUID(16).Lower());
|
|
Json.Object().AddPair("host", caHost);
|
|
|
|
if (!caOrigin.IsEmpty()) {
|
|
Json.Object().AddPair("origin", caOrigin);
|
|
}
|
|
|
|
Json.Object().AddPair("module", Module);
|
|
Json.Object().AddPair("address", caUserAddress.IsEmpty() ? caModuleAddress : caUserAddress);
|
|
|
|
if (!caDealCode.IsEmpty()) {
|
|
CJSONValue Deal(jvtObject);
|
|
Deal.Object().AddPair("code", caDealCode);
|
|
Json.Object().AddPair("deal", Deal);
|
|
}
|
|
|
|
if (!sPayload.IsEmpty())
|
|
Json.Object().AddPair("payload", base64_encode(sPayload));
|
|
|
|
pProxyRequest->Clear();
|
|
|
|
pProxyRequest->Location = pServerRequest->Location;
|
|
pProxyRequest->CloseConnection = true;
|
|
pProxyRequest->ContentType = CHTTPRequest::json;
|
|
pProxyRequest->Content << Json;
|
|
|
|
CHTTPRequest::Prepare(pProxyRequest, Method.c_str(), URI.c_str());
|
|
|
|
pProxyRequest->AddHeader("Authorization", "Bearer " + m_CurrentServer.Tokens()["access_token"]);
|
|
|
|
if (!caModuleAddress.IsEmpty())
|
|
pProxyRequest->AddHeader("Module-Address", caModuleAddress);
|
|
|
|
if (!caModuleFee.IsEmpty())
|
|
pProxyRequest->AddHeader("Module-Fee", caModuleFee);
|
|
|
|
if (!caOrigin.IsEmpty())
|
|
pProxyRequest->AddHeader("Origin", caOrigin);
|
|
|
|
AConnection->TimeOutInterval(30 * 1000);
|
|
AConnection->UpdateTimeOut(Now());
|
|
|
|
AConnection->CloseConnection(false);
|
|
|
|
pProxy->Active(true);
|
|
}
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
|
|
void CWebService::DoSignature(CHTTPServerConnection *AConnection) {
|
|
auto pRequest = AConnection->Request();
|
|
auto pReply = AConnection->Reply();
|
|
|
|
if (pRequest->Content.IsEmpty()) {
|
|
AConnection->SendStockReply(CHTTPReply::no_content);
|
|
return;
|
|
}
|
|
|
|
auto &Keys = m_CurrentServer.PGP();
|
|
|
|
int index = 0;
|
|
while (index < Keys.Count() && Keys[index].Name != m_CurrentServer.Name()) {
|
|
index++;
|
|
}
|
|
|
|
if (index == Keys.Count())
|
|
throw ExceptionFrm("PGP key not found.");
|
|
|
|
const auto &caServerKey = Keys[index].Key;
|
|
|
|
if (caServerKey.IsEmpty())
|
|
throw ExceptionFrm("PGP key not added.");
|
|
|
|
CString message;
|
|
CJSON Json(jvtObject);
|
|
|
|
const auto &caContentType = pRequest->Headers["content-type"];
|
|
|
|
if (caContentType.Find("application/x-www-form-urlencoded") == 0) {
|
|
const CStringList &FormData = pRequest->FormData;
|
|
|
|
const auto &caClearText = FormData["message"];
|
|
CheckKeyForNull("message", caClearText.c_str());
|
|
|
|
const auto bVerified = CheckVerifyPGPSignature(VerifyPGPSignature(caClearText, caServerKey, message), message);
|
|
Json.Object().AddPair("verified", bVerified);
|
|
} else if (caContentType.Find("multipart/form-data") == 0) {
|
|
CFormData FormData;
|
|
CHTTPRequestParser::ParseFormData(pRequest, FormData);
|
|
|
|
const auto &caClearText = FormData.Data("message");
|
|
CheckKeyForNull("message", caClearText.c_str());
|
|
|
|
const auto bVerified = CheckVerifyPGPSignature(VerifyPGPSignature(caClearText, caServerKey, message), message);
|
|
Json.Object().AddPair("verified", bVerified);
|
|
} else if (caContentType.Find("application/json") == 0) {
|
|
const CJSON jsonData(pRequest->Content);
|
|
|
|
const auto &caClearText = jsonData["message"].AsString();
|
|
CheckKeyForNull("message", caClearText.c_str());
|
|
|
|
const auto bVerified = CheckVerifyPGPSignature(VerifyPGPSignature(caClearText, caServerKey, message), message);
|
|
Json.Object().AddPair("verified", bVerified);
|
|
} else {
|
|
const auto &caClearText = pRequest->Content;
|
|
const auto bVerified = CheckVerifyPGPSignature(VerifyPGPSignature(caClearText, caServerKey, message), message);
|
|
Json.Object().AddPair("verified", bVerified);
|
|
}
|
|
|
|
Json.Object().AddPair("message", message);
|
|
|
|
AConnection->CloseConnection(true);
|
|
pReply->Content << Json;
|
|
|
|
AConnection->SendReply(CHTTPReply::ok);
|
|
}
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
|
|
void CWebService::FetchPGP(CContext &Context) {
|
|
|
|
Log()->Notice("[%s] [%s] Trying to fetch a PGP key.", Context.Name().c_str(), Context.URL().Origin().c_str());
|
|
|
|
auto OnRequest = [&Context](CHTTPClient *Sender, CHTTPRequest *ARequest) {
|
|
|
|
const auto &name = Sender->Data()["name"];
|
|
auto &Keys = Context.PGP();
|
|
|
|
int index = 0;
|
|
while (index < Keys.Count() && Keys[index].Name != name) {
|
|
index++;
|
|
}
|
|
|
|
ARequest->ContentType = CHTTPRequest::json;
|
|
|
|
if (index < Keys.Count()) {
|
|
auto &Key = Keys[index];
|
|
Key.Status = CKeyContext::ksFetching;
|
|
Key.StatusTime = Now();
|
|
}
|
|
|
|
ARequest->Content = CString().Format(R"({"type": "pgp", "account": "%s", "code": "%s", "fields": ["code", "data"]})", Context.Name().c_str(), name.c_str());
|
|
|
|
CHTTPRequest::Prepare(ARequest, "POST", "/api/v1/key/public");
|
|
|
|
const auto &access_token = Context.Tokens()["access_token"];
|
|
|
|
if (!access_token.empty()) {
|
|
ARequest->AddHeader("Authorization", "Bearer " + access_token);
|
|
}
|
|
|
|
DebugRequest(ARequest);
|
|
};
|
|
|
|
auto OnExecute = [this, &Context](CTCPConnection *AConnection) {
|
|
|
|
auto pConnection = dynamic_cast<CHTTPClientConnection *> (AConnection);
|
|
|
|
if (pConnection != nullptr) {
|
|
auto pClient = dynamic_cast<CHTTPClient *> (pConnection->Client());
|
|
auto pReply = pConnection->Reply();
|
|
|
|
DebugReply(pReply);
|
|
|
|
CString Content;
|
|
|
|
try {
|
|
JsonStringToKey(pReply->Content, Content);
|
|
|
|
if (Content.IsEmpty())
|
|
throw Delphi::Exception::Exception("Not found.");
|
|
|
|
const auto &name = pClient->Data()["name"];
|
|
auto &Keys = Context.PGP();
|
|
|
|
int index = 0;
|
|
while (index < Keys.Count() && Keys[index].Name != name) {
|
|
index++;
|
|
}
|
|
|
|
if (index < Keys.Count()) {
|
|
auto &Key = Keys[index];
|
|
|
|
Key.Status = CKeyContext::ksSuccess;
|
|
Key.StatusTime = Now();
|
|
Key.Key = Content;
|
|
|
|
UpdateServerList(m_Servers, Content);
|
|
}
|
|
|
|
Context.SetStatus(csRunning);
|
|
} catch (Delphi::Exception::Exception &e) {
|
|
Log()->Error(APP_LOG_INFO, 0, "[FetchPGP] %s", e.what());
|
|
}
|
|
|
|
pConnection->CloseConnection(true);
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
auto OnException = [&Context](CTCPConnection *AConnection, const Delphi::Exception::Exception &E) {
|
|
auto pConnection = dynamic_cast<CHTTPClientConnection *> (AConnection);
|
|
if (pConnection != nullptr) {
|
|
auto pClient = dynamic_cast<CHTTPClient *> (pConnection->Client());
|
|
if (pClient != nullptr) {
|
|
const auto &name = pClient->Data()["name"];
|
|
auto &Keys = Context.PGP();
|
|
|
|
int index = 0;
|
|
while (index < Keys.Count() && Keys[index].Name != name) {
|
|
index++;
|
|
}
|
|
|
|
if (index < Keys.Count()) {
|
|
auto &Key = Keys[index];
|
|
Key.Status = CKeyContext::ksError;
|
|
Key.StatusTime = Now();
|
|
}
|
|
|
|
Log()->Error(APP_LOG_EMERG, 0, "[%s:%d] %s", pClient->Host().c_str(), pClient->Port(), E.what());
|
|
}
|
|
DebugReply(pConnection->Reply());
|
|
}
|
|
|
|
Context.SetStatus(Context::csAuthorized);
|
|
};
|
|
|
|
auto &Keys = Context.PGP();
|
|
|
|
for (int i = 0; i < Keys.Count(); i++ ) {
|
|
auto &Key = Keys[i];
|
|
|
|
Key.Status = CKeyContext::ksUnknown;
|
|
Key.StatusTime = Now();
|
|
Key.RunTime = Key.StatusTime;
|
|
|
|
auto pClient = GetClient(Context.URL().hostname, Context.URL().port == 0 ? BPS_SERVER_PORT : Context.URL().port);
|
|
|
|
pClient->Data().AddPair("name", Key.Name);
|
|
|
|
pClient->OnRequest(OnRequest);
|
|
pClient->OnExecute(OnExecute);
|
|
pClient->OnException(OnException);
|
|
|
|
pClient->AutoFree(true);
|
|
pClient->Active(true);
|
|
}
|
|
}
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
|
|
void CWebService::Reload() {
|
|
Config()->IniFile().ReadSectionValues("module", &m_Module);
|
|
|
|
CString FileName;
|
|
|
|
FileName = Config()->IniFile().ReadString(CONFIG_SECTION_NAME, "oauth2", "oauth2/service.json");
|
|
if (!path_separator(FileName.front())) {
|
|
FileName = Config()->Prefix() + FileName;
|
|
}
|
|
|
|
if (FileExists(FileName.c_str())) {
|
|
m_OAuth2.LoadFromFile(FileName.c_str());
|
|
}
|
|
|
|
const auto &caPrivateKey = Config()->IniFile().ReadString("pgp", "private", "module.sec");
|
|
const auto &caPublicKey = Config()->IniFile().ReadString("pgp", "public", "dm.pub");
|
|
|
|
m_pgpPassphrase = Config()->IniFile().ReadString("pgp", "passphrase", "");
|
|
|
|
if (FileExists(caPrivateKey.c_str())) {
|
|
m_pgpModuleKey.LoadFromFile(caPrivateKey.c_str());
|
|
|
|
if (FileExists(caPublicKey.c_str())) {
|
|
m_pgpPublicKey.LoadFromFile(caPublicKey.c_str());
|
|
UpdateServerList(m_Servers, m_pgpPublicKey);
|
|
} else {
|
|
Log()->Error(APP_LOG_WARN, 0, APP_FILE_NOT_FOUND, caPublicKey.c_str());
|
|
}
|
|
} else {
|
|
Log()->Error(APP_LOG_WARN, 0, APP_FILE_NOT_FOUND, caPrivateKey.c_str());
|
|
}
|
|
}
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
|
|
void CWebService::Heartbeat(CDateTime Now) {
|
|
for (int i = 0; i < m_Servers.Count(); i++) {
|
|
auto &Context = m_Servers[i].Value();
|
|
#ifndef _DEBUG
|
|
if (Context.Name() == BPS_BM_DEBUG_ADDRESS)
|
|
continue;
|
|
#endif
|
|
if (Now >= Context.CheckDate()) {
|
|
Context.SetCheckDate(Now + (CDateTime) 30 / SecsPerDay); // 30 sec
|
|
|
|
if (Context.Status() == Context::csInitialization) {
|
|
ModuleService(Context);
|
|
}
|
|
|
|
if (Context.Status() >= Context::csInitialized) {
|
|
CheckProviders(Now, Context);
|
|
FetchProviders(Now, Context);
|
|
}
|
|
}
|
|
|
|
if (Now >= Context.FixedDate()) {
|
|
if (Context.Status() == Context::csAuthorized) {
|
|
Context.SetFixedDate(Now + (CDateTime) 30 / SecsPerDay); // 30 sec
|
|
Context.SetStatus(Context::csInProgress);
|
|
|
|
FetchPGP(Context);
|
|
}
|
|
}
|
|
|
|
if (Context.Status() == Context::csRunning) {
|
|
if (Now >= m_NexServerTime) {
|
|
m_NexServerTime = GetRandomDate(10 * 60, m_SyncPeriod * 60, Now);
|
|
m_CurrentServer = Context;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
|
|
}
|
|
}
|
|
} |