Files
apostol-dm/src/modules/Workers/WebService/WebService.cpp
Преподобный Ален 16e16884fd Committing updates.
2023-02-06 15:33:00 +03:00

1257 lines
52 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
}
}
//--------------------------------------------------------------------------------------------------------------
int CWebService::CurrentContextIndex(const CString &Params) {
int index = 0;
while (index < m_Servers.Count()) {
const auto &caContext = m_Servers[index].Value();
if (caContext.Status() == Context::csRunning) {
if (Params == caContext.Name() || Params == caContext.URL().Origin()) {
return index;
}
}
index++;
}
return -1;
}
//--------------------------------------------------------------------------------------------------------------
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();
const auto &caServerRequest = pProxyConnection->Request();
auto &ServerReply = pProxyConnection->Reply();
auto &ProxyReply = pConnection->Reply();
const auto &caFormat = caServerRequest.Params["payload"];
if (!caFormat.IsEmpty()) {
if (caFormat == "html") {
ServerReply.ContentType = CHTTPReply::html;
} else if (caFormat == "json") {
ServerReply.ContentType = CHTTPReply::json;
} else if (caFormat == "xml") {
ServerReply.ContentType = CHTTPReply::xml;
} else {
ServerReply.ContentType = CHTTPReply::text;
}
if (ProxyReply.Status == CHTTPReply::ok) {
if (!ProxyReply.Content.IsEmpty()) {
const CJSON json(ProxyReply.Content);
ServerReply.Content = base64_decode(json["payload"].AsString());
}
pProxyConnection->SendReply(ProxyReply.Status, nullptr, true);
} else {
if (ProxyReply.Status == CHTTPReply::forbidden) {
const auto &caServerParam = caServerRequest.Params["server"];
const auto index = CurrentContextIndex(caServerParam);
auto &Context = index == -1 ? m_CurrentServer : m_Servers[index].Value();
Context.SetCheckDate(0);
Context.SetStatus(Context::csInitialized);
}
ServerReply.Content = ProxyReply.Content;
pProxyConnection->SendStockReply(ProxyReply.Status, true);
}
} else {
ServerReply.Content = ProxyReply.Content;
if (ProxyReply.Status == CHTTPReply::ok) {
pProxyConnection->SendReply(ProxyReply.Status, nullptr, true);
} else {
pProxyConnection->SendStockReply(ProxyReply.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();
const auto &caServerRequest = pProxyConnection->Request();
auto &ServerReply = pProxyConnection->Reply();
const auto &Format = caServerRequest.Params["format"];
if (Format == "html") {
ServerReply.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 &Reply = pProxyConnection->Reply();
ExceptionToJson(0, E, Reply.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);
const auto &caServerRequest = AConnection->Request();
auto &ProxyRequest = pProxy->Request();
const auto &caModuleAddress = m_Module["address"];
const auto &caHost = caServerRequest.Headers["host"];
const auto &caOrigin = caServerRequest.Headers["origin"];
const auto &caUserAddress = GetAddress(caServerRequest);
const auto &pgpValue = caServerRequest.Params["pgp"];
const auto &caServerParam = caServerRequest.Params["server"];
const auto index = CurrentContextIndex(caServerParam);
const auto &caContext = index == -1 ? m_CurrentServer : m_Servers[index].Value();
const auto &caServer = caContext.URL().Origin();
CLocation Location(caServer);
pProxy->Host() = Location.hostname;
pProxy->Port(Location.port == 0 ? BPS_SERVER_PORT : Location.port);
CStringList caClearText;
CString sPayload;
if (!caServerRequest.Content.IsEmpty()) {
const auto &ContentType = caServerRequest.Headers.Values(_T("content-type"));
if (ContentType.Find("application/x-www-form-urlencoded") == 0) {
const CStringList &FormData = caServerRequest.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(caServerRequest, 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(caServerRequest.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 = caServerRequest.Content;
}
if (pgpValue == "off" || pgpValue == "false") {
sPayload = caClearText.Text();
} else {
Apostol::PGP::CleartextSignature(
m_pgpModuleKey,
m_pgpPassphrase,
BPS_PGP_HASH,
caClearText.Text(),
sPayload);
}
}
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));
ProxyRequest.Clear();
ProxyRequest.Location = caServerRequest.Location;
ProxyRequest.CloseConnection = false;
ProxyRequest.ContentType = CHTTPRequest::json;
ProxyRequest.Content << Json;
CHTTPRequest::Prepare(ProxyRequest, "POST", URI.c_str());
ProxyRequest.AddHeader("Authorization", "Bearer " + caContext.Tokens()["access_token"]);
if (!caModuleAddress.IsEmpty())
ProxyRequest.AddHeader("Module-Address", caModuleAddress);
if (!caOrigin.IsEmpty())
ProxyRequest.AddHeader("Origin", caOrigin);
AConnection->CloseConnection(false);
pProxy->AutoFree(true);
pProxy->Active(true);
}
//--------------------------------------------------------------------------------------------------------------
void CWebService::DoDeal(CHTTPServerConnection *AConnection, const CString &Method, const CString &URI, const CString &Action) {
auto pProxy = GetProxy(AConnection);
const auto &caServerRequest = AConnection->Request();
auto &ProxyRequest = 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 = caServerRequest.Headers["host"];
const auto &caOrigin = caServerRequest.Headers["origin"];
const auto &address = GetAddress(caServerRequest);
const auto &code = caServerRequest.Params["code"];
const auto &caUserAddress = address.length() == 40 ? CString() : address;
const auto &caDealCode = !code.empty() ? code : address.length() == 40 ? address : CString();
const auto &pgpValue = caServerRequest.Params["pgp"];
const auto &caServerParam = caServerRequest.Params["server"];
const auto index = CurrentContextIndex(caServerParam);
const auto &caContext = index == -1 ? m_CurrentServer : m_Servers[index].Value();
const auto &caServer = caContext.URL().Origin();
CLocation Location(caServer);
pProxy->Host() = Location.hostname;
pProxy->Port(Location.port == 0 ? BPS_SERVER_PORT : Location.port);
YAML::Node Node;
CString sPayload;
if (!caServerRequest.Content.IsEmpty() && Action != "status") {
const auto &ContentType = caServerRequest.Headers.Values(_T("content-type"));
if (ContentType.Find("application/x-www-form-urlencoded") == 0) {
const CStringList &FormData = caServerRequest.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"];
const auto &formFeedbackRefund = FormData["feedback_refund"];
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.IsEmpty() ? UTCFormat(DateToString(UTC())).c_str() : 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();
if (!formFeedbackRefund.IsEmpty())
Feedback["refund"] = formFeedbackRefund.c_str();
}
} else if (ContentType.Find("multipart/form-data") == 0) {
CFormData FormData;
CHTTPRequestParser::ParseFormData(caServerRequest, 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");
const auto &formFeedbackRefund = FormData.Data("feedback_refund");
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.IsEmpty() ? UTCFormat(DateToString(UTC())).c_str() : 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();
if (!formFeedbackRefund.IsEmpty())
Feedback["refund"] = formFeedbackRefund.c_str();
}
} else if (ContentType.Find("application/json") == 0) {
const CJSON jsonData(caServerRequest.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 &formFeedbackRefund = jsonFeedback["refund"].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.IsEmpty() ? UTCFormat(DateToString(UTC())).c_str() : 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();
if (!formFeedbackRefund.IsEmpty())
Feedback["refund"] = formFeedbackRefund.c_str();
}
} else {
Node = YAML::Load(caServerRequest.Content.c_str());
}
const auto &BTCKeys = caContext.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) {
const auto isSegWit = IsSegWitAddress(Data.Seller.Address) && IsSegWitAddress(Data.Customer.Address);
Data.Payment.Address = isSegWit ?
Deal.GetPaymentSW(BTCKeys.Names(0), BTCKeys.Names(1), Deal.Data().Transaction.Key, BitcoinConfig.version_hd) :
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));
ProxyRequest.Clear();
ProxyRequest.Location = caServerRequest.Location;
ProxyRequest.CloseConnection = true;
ProxyRequest.ContentType = CHTTPRequest::json;
ProxyRequest.Content << Json;
CHTTPRequest::Prepare(ProxyRequest, "POST", URI.c_str());
ProxyRequest.AddHeader("Authorization", "Bearer " + caContext.Tokens()["access_token"]);
if (!caModuleAddress.IsEmpty())
ProxyRequest.AddHeader("Module-Address", caModuleAddress);
if (!caModuleFee.IsEmpty())
ProxyRequest.AddHeader("Module-Fee", caModuleFee);
if (!caOrigin.IsEmpty())
ProxyRequest.AddHeader("Origin", caOrigin);
AConnection->TimeOutInterval(30 * 1000);
AConnection->UpdateTimeOut(Now());
AConnection->CloseConnection(false);
pProxy->AutoFree(true);
pProxy->Active(true);
}
//--------------------------------------------------------------------------------------------------------------
void CWebService::DoProxy(CHTTPServerConnection *AConnection, const CString &Method, const CString &URI) {
auto pProxy = GetProxy(AConnection);
const auto &caServerRequest = AConnection->Request();
auto &ProxyRequest = 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 = caServerRequest.Headers["host"];
const auto &caOrigin = caServerRequest.Headers["origin"];
const auto &caContentType = caServerRequest.Headers["content-type"].Lower();
const auto &address = GetAddress(caServerRequest);
const auto &code = caServerRequest.Params["code"];
const auto &caUserAddress = address.length() == 40 ? CString() : address;
const auto &caDealCode = !code.empty() ? code : address.length() == 40 ? address : CString();
const auto &pgpValue = caServerRequest.Params["pgp"];
const auto &caServerParam = caServerRequest.Params["server"];
const auto index = CurrentContextIndex(caServerParam);
const auto &caContext = index == -1 ? m_CurrentServer : m_Servers[index].Value();
const auto &caServer = caContext.URL().Origin();
CLocation Location(caServer);
pProxy->Host() = Location.hostname;
pProxy->Port(Location.port == 0 ? BPS_SERVER_PORT : Location.port);
ProxyRequest.Clear();
ProxyRequest.Location = caServerRequest.Location;
if (Method == "POST") {
ProxyRequest.ContentType = StringToContentType(caContentType);
ProxyRequest.Content = caServerRequest.Content;
}
ProxyRequest.CloseConnection = true;
CHTTPRequest::Prepare(ProxyRequest, Method.c_str(), URI.c_str(), caContentType.c_str());
ProxyRequest.AddHeader("Authorization", "Bearer " + caContext.Tokens()["access_token"]);
if (!caModuleAddress.IsEmpty())
ProxyRequest.AddHeader("Module-Address", caModuleAddress);
if (!caModuleFee.IsEmpty())
ProxyRequest.AddHeader("Module-Fee", caModuleFee);
if (!caOrigin.IsEmpty())
ProxyRequest.AddHeader("Origin", caOrigin);
AConnection->TimeOutInterval(15 * 1000);
AConnection->UpdateTimeOut(Now());
AConnection->CloseConnection(false);
pProxy->AutoFree(true);
pProxy->Active(true);
}
//--------------------------------------------------------------------------------------------------------------
void CWebService::DoSignature(CHTTPServerConnection *AConnection) {
const auto &caRequest = AConnection->Request();
auto &Reply = AConnection->Reply();
if (caRequest.Content.IsEmpty()) {
AConnection->SendStockReply(CHTTPReply::no_content);
return;
}
const auto &caServerParam = caRequest.Params["server"];
int index = CurrentContextIndex(caServerParam);
const auto &caContext = index == -1 ? m_CurrentServer : m_Servers[index].Value();
auto &Keys = caContext.PGP();
index = 0;
while (index < Keys.Count() && Keys[index].Name != caContext.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 = caRequest.Headers["content-type"];
if (caContentType.Find("application/x-www-form-urlencoded") == 0) {
const auto &FormData = caRequest.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(caRequest, 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(caRequest.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 = caRequest.Content;
const auto bVerified = CheckVerifyPGPSignature(VerifyPGPSignature(caClearText, caServerKey, message), message);
Json.Object().AddPair("verified", bVerified);
}
Json.Object().AddPair("message", message);
AConnection->CloseConnection(true);
Reply.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 &Request) {
const auto &name = Sender->Data()["name"];
auto &Keys = Context.PGP();
int index = 0;
while (index < Keys.Count() && Keys[index].Name != name) {
index++;
}
Request.ContentType = CHTTPRequest::json;
if (index < Keys.Count()) {
auto &Key = Keys[index];
Key.Status = CKeyContext::ksFetching;
Key.StatusTime = Now();
}
Request.Content = CString().Format(R"({"type": "pgp", "account": "%s", "code": "%s", "fields": ["code", "data"]})", Context.Name().c_str(), name.c_str());
CHTTPRequest::Prepare(Request, "POST", "/api/v1/key/public");
const auto &access_token = Context.Tokens()["access_token"];
if (!access_token.empty()) {
Request.AddHeader("Authorization", "Bearer " + access_token);
}
DebugRequest(Request);
};
auto OnExecute = [this, &Context](CTCPConnection *AConnection) {
auto pConnection = dynamic_cast<CHTTPClientConnection *> (AConnection);
if (pConnection != nullptr) {
auto pClient = dynamic_cast<CHTTPClient *> (pConnection->Client());
const auto &Reply = pConnection->Reply();
DebugReply(Reply);
CString Code;
CString Key;
try {
JsonStringToKey(Reply.Content, Code, Key);
if (Key.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 &PGP = Keys[index];
PGP.Status = CKeyContext::ksSuccess;
PGP.StatusTime = Now();
PGP.Key = Key;
if (Code == "PUBLIC") {
UpdateServerList(m_Servers, Key);
Context.SetStatus(csRunning);
}
}
} catch (Delphi::Exception::Exception &e) {
Context.SetStatus(Context::csAuthorized);
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(Context, Now);
}
}
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;
}
}
}
}
//--------------------------------------------------------------------------------------------------------------
}
}
}