2022 lines
83 KiB
C++
2022 lines
83 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): CApostolModule(AProcess, "web service", "module/WebService") {
|
|
m_NexServerTime = 0;
|
|
m_SyncPeriod = BPS_DEFAULT_SYNC_PERIOD;
|
|
m_Status = psStopped;
|
|
|
|
CWebService::InitMethods();
|
|
}
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
|
|
void CWebService::InitMethods() {
|
|
#if defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE >= 9)
|
|
m_pMethods->AddObject(_T("GET") , (CObject *) new CMethodHandler(true , [this](auto && Connection) { DoGet(Connection); }));
|
|
m_pMethods->AddObject(_T("POST") , (CObject *) new CMethodHandler(true , [this](auto && Connection) { DoPost(Connection); }));
|
|
m_pMethods->AddObject(_T("HEAD") , (CObject *) new CMethodHandler(true , [this](auto && Connection) { DoHead(Connection); }));
|
|
m_pMethods->AddObject(_T("OPTIONS"), (CObject *) new CMethodHandler(true , [this](auto && Connection) { DoOptions(Connection); }));
|
|
m_pMethods->AddObject(_T("PUT") , (CObject *) new CMethodHandler(false, [this](auto && Connection) { MethodNotAllowed(Connection); }));
|
|
m_pMethods->AddObject(_T("DELETE") , (CObject *) new CMethodHandler(false, [this](auto && Connection) { MethodNotAllowed(Connection); }));
|
|
m_pMethods->AddObject(_T("TRACE") , (CObject *) new CMethodHandler(false, [this](auto && Connection) { MethodNotAllowed(Connection); }));
|
|
m_pMethods->AddObject(_T("PATCH") , (CObject *) new CMethodHandler(false, [this](auto && Connection) { MethodNotAllowed(Connection); }));
|
|
m_pMethods->AddObject(_T("CONNECT"), (CObject *) new CMethodHandler(false, [this](auto && Connection) { MethodNotAllowed(Connection); }));
|
|
#else
|
|
m_pMethods->AddObject(_T("GET"), (CObject *) new CMethodHandler(true, std::bind(&CWebService::DoGet, this, _1)));
|
|
m_pMethods->AddObject(_T("POST"), (CObject *) new CMethodHandler(true, std::bind(&CWebService::DoPost, this, _1)));
|
|
m_pMethods->AddObject(_T("HEAD"), (CObject *) new CMethodHandler(true, std::bind(&CWebService::DoHead, this, _1)));
|
|
m_pMethods->AddObject(_T("OPTIONS"), (CObject *) new CMethodHandler(true, std::bind(&CWebService::DoOptions, this, _1)));
|
|
m_pMethods->AddObject(_T("PUT"), (CObject *) new CMethodHandler(false, std::bind(&CWebService::MethodNotAllowed, this, _1)));
|
|
m_pMethods->AddObject(_T("DELETE"), (CObject *) new CMethodHandler(false, std::bind(&CWebService::MethodNotAllowed, this, _1)));
|
|
m_pMethods->AddObject(_T("TRACE"), (CObject *) new CMethodHandler(false, std::bind(&CWebService::MethodNotAllowed, this, _1)));
|
|
m_pMethods->AddObject(_T("PATCH"), (CObject *) new CMethodHandler(false, std::bind(&CWebService::MethodNotAllowed, this, _1)));
|
|
m_pMethods->AddObject(_T("CONNECT"), (CObject *) new CMethodHandler(false, std::bind(&CWebService::MethodNotAllowed, this, _1)));
|
|
#endif
|
|
}
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
|
|
void CWebService::InitServerList() {
|
|
m_DefaultServer.Value().Name() = m_DefaultServer.Name();
|
|
m_DefaultServer.Value().PGP().Name = "PUBLIC";
|
|
|
|
if (m_Servers.Count() == 0) {
|
|
#ifdef _DEBUG
|
|
int index = m_Servers.AddPair(BPS_BM_DEBUG_ADDRESS, CContext(CLocation(BPS_SERVER_URL)));
|
|
m_Servers[index].Value().Name() = m_Servers[index].Name();
|
|
m_Servers[index].Value().PGP().Name = "PUBLIC";
|
|
#else
|
|
m_Servers.Add(m_DefaultServer);
|
|
#endif
|
|
}
|
|
}
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
|
|
void CWebService::UpdateServerList(const CString &Key) {
|
|
CStringPairs ServerList;
|
|
CStringList Keys;
|
|
|
|
ParsePGPKey(Key, ServerList, Keys);
|
|
|
|
if (ServerList.Count() != 0) {
|
|
CStringPairs::ConstEnumerator em(ServerList);
|
|
while (em.MoveNext()) {
|
|
const auto ¤t = em.Current();
|
|
if (m_Servers.IndexOfName(current.Name()) == -1 && !current.Value().IsEmpty()) {
|
|
m_Servers.AddPair(current.Name(), CContext(CLocation(current.Value())));
|
|
auto &Context = m_Servers.Last().Value();
|
|
Context.Name() = current.Name();
|
|
Context.PGP().Name = "PUBLIC";
|
|
Context.PGP().Key = Key;
|
|
Context.BTCKeys() = Keys;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
|
|
void CWebService::CheckKeyForNull(LPCTSTR Key, LPCTSTR Value) {
|
|
if (Value == nullptr)
|
|
throw ExceptionFrm("Invalid format: key \"%s\" cannot be empty.", Key);
|
|
}
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
|
|
void CWebService::UpdateOAuth2(CContext &Context, const CJSONObject &OAuth2) {
|
|
if (OAuth2["type"].AsString() == "service_account") {
|
|
UpdateProviders(Context.Providers(), OAuth2);
|
|
|
|
Context.SetCheckDate(0);
|
|
Context.SetStatus(Context::csInitialized);
|
|
}
|
|
}
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
|
|
void CWebService::UpdateProviders(CProviders &Providers, const CJSONObject &Data) {
|
|
const auto &caProviderName = CString(SYSTEM_PROVIDER_NAME);
|
|
const auto &caApplicationName = CString(SERVICE_APPLICATION_NAME);
|
|
|
|
int index = Providers.IndexOfName(caProviderName);
|
|
if (index == -1)
|
|
index = Providers.AddPair(caProviderName, CProvider(caProviderName));
|
|
auto& Provider = Providers[index].Value();
|
|
Provider.Applications().AddPair(caApplicationName, Data);
|
|
}
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
|
|
bool CWebService::FindURLInLine(const CString &Line, CStringList &List) {
|
|
CString URL;
|
|
|
|
TCHAR ch;
|
|
int length = 0;
|
|
size_t startPos, pos;
|
|
|
|
pos = 0;
|
|
while ((startPos = Line.Find(HTTP_PREFIX, pos)) != CString::npos) {
|
|
|
|
URL.Clear();
|
|
|
|
pos = startPos + HTTP_PREFIX_SIZE;
|
|
if (Line.Length() < 5)
|
|
return false;
|
|
|
|
URL.Append(HTTP_PREFIX);
|
|
ch = Line.at(pos);
|
|
if (ch == 's') {
|
|
URL.Append(ch);
|
|
pos++;
|
|
}
|
|
|
|
if (Line.Length() < 7 || Line.at(pos++) != ':' || Line.at(pos++) != '/' || Line.at(pos++) != '/')
|
|
return false;
|
|
|
|
URL.Append("://");
|
|
|
|
length = 0;
|
|
ch = Line.at(pos);
|
|
while (ch != 0 && (IsChar(ch) || IsNumeral(ch) || ch == ':' || ch == '.' || ch == '-')) {
|
|
URL.Append(ch);
|
|
length++;
|
|
ch = Line.at(++pos);
|
|
}
|
|
|
|
if (length < 3) {
|
|
return false;
|
|
}
|
|
|
|
if (startPos == 0) {
|
|
List.Add(URL);
|
|
} else {
|
|
ch = Line.at(startPos - 1);
|
|
switch (ch) {
|
|
case ' ':
|
|
case ',':
|
|
case ';':
|
|
List.Add(URL);
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
|
|
int CWebService::CheckFee(const CString &Fee) {
|
|
if (!Fee.IsEmpty()) {
|
|
|
|
if (Fee.Length() >= 10)
|
|
return -1;
|
|
|
|
size_t numbers = 0;
|
|
size_t delimiter = 0;
|
|
size_t percent = 0;
|
|
|
|
size_t pos = 0;
|
|
TCHAR ch;
|
|
|
|
ch = Fee.at(pos);
|
|
while (ch != 0) {
|
|
if (IsNumeral(ch))
|
|
numbers++;
|
|
if (ch == '.')
|
|
delimiter++;
|
|
if (ch == '%')
|
|
percent++;
|
|
ch = Fee.at(++pos);
|
|
}
|
|
|
|
if (numbers == 0 || delimiter > 1 || percent > 1 || ((numbers + percent + delimiter) != Fee.Length()))
|
|
return -1;
|
|
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
|
|
void CWebService::CreateAccessToken(const CProvider &Provider, const CString &Application, CContext &Context) {
|
|
|
|
auto OnDone = [&Context](CTCPConnection *Sender) {
|
|
|
|
auto pConnection = dynamic_cast<CHTTPClientConnection *> (Sender);
|
|
auto pReply = pConnection->Reply();
|
|
|
|
DebugReply(pReply);
|
|
|
|
if (pReply->Status == CHTTPReply::ok) {
|
|
const CJSON Json(pReply->Content);
|
|
|
|
Context.SetStatus(csAuthorized);
|
|
|
|
Context.Session() = Json["session"].AsString();
|
|
Context.Secret() = Json["secret"].AsString();
|
|
|
|
Context.Tokens().Values("access_token", Json["access_token"].AsString());
|
|
|
|
Context.SetFixedDate(0);
|
|
Context.SetCheckDate(Now() + (CDateTime) 50 / MinsPerDay); // 50 min
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
auto OnFail = [&Context](CTCPConnection *Sender, const Delphi::Exception::Exception &E) {
|
|
Context.SetStatus(csInitialized);
|
|
|
|
auto pConnection = dynamic_cast<CHTTPClientConnection *> (Sender);
|
|
auto pClient = dynamic_cast<CHTTPClient *> (pConnection->Client());
|
|
|
|
DebugReply(pConnection->Reply());
|
|
|
|
Log()->Error(APP_LOG_ERR, 0, "[%s:%d] %s", pClient->Host().c_str(), pClient->Port(), E.what());
|
|
};
|
|
|
|
auto OnHTTPClient = [this](const CLocation &URI) {
|
|
return GetClient(URI.hostname, URI.port);
|
|
};
|
|
|
|
CString server_uri(Context.URL().Origin());
|
|
|
|
const auto &token_uri = Provider.TokenURI(Application);
|
|
const auto &service_token = CToken::CreateToken(Provider, Application);
|
|
|
|
Context.Tokens().Values("service_token", service_token);
|
|
|
|
if (!token_uri.IsEmpty()) {
|
|
CToken::FetchAccessToken(token_uri.front() == '/' ? server_uri + token_uri : token_uri, service_token, OnHTTPClient, OnDone, OnFail);
|
|
}
|
|
}
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
|
|
void CWebService::FetchProviders(CDateTime Now, CContext &Context) {
|
|
for (int i = 0; i < Context.Providers().Count(); i++) {
|
|
auto &Provider = Context.Providers()[i].Value();
|
|
for (int j = 0; j < Provider.Applications().Count(); ++j) {
|
|
const auto &app = Provider.Applications().Members(j);
|
|
if (app["type"].AsString() == "service_account") {
|
|
if (Provider.KeyStatus() == ksUnknown) {
|
|
CreateAccessToken(Provider, app.String(), Context);
|
|
Provider.KeyStatusTime(Now);
|
|
Provider.KeyStatus(ksSuccess);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
|
|
void CWebService::CheckProviders(CDateTime Now, CContext &Context) {
|
|
for (int i = 0; i < Context.Providers().Count(); i++) {
|
|
auto& Provider = Context.Providers()[i].Value();
|
|
if (Provider.KeyStatus() != ksUnknown) {
|
|
Provider.KeyStatusTime(Now);
|
|
Provider.KeyStatus(ksUnknown);
|
|
}
|
|
}
|
|
}
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
|
|
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& 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);
|
|
|
|
Json.Object().AddPair("id", ApostolUID());
|
|
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::CheckDeal(const CDeal &Deal) {
|
|
|
|
const auto& Data = Deal.Data();
|
|
|
|
const auto DateTime = UTC();
|
|
const auto Date = StringToDate(Data.Date);
|
|
const auto Sum = BTCToDouble(Data.Payment.Sum);
|
|
|
|
if (Data.Order == doCreate) {
|
|
if (DateTime < Date)
|
|
throw ExceptionFrm("Invalid deal date.");
|
|
|
|
if ((DateTime - Date) > (CDateTime) 180 / SecsPerDay)
|
|
throw ExceptionFrm("Deal date expired.");
|
|
}
|
|
|
|
if (Data.Order == doComplete) {
|
|
const CDateTime LeaveBefore = StringToDate(Data.FeedBack.LeaveBefore);
|
|
if (DateTime > LeaveBefore)
|
|
throw ExceptionFrm("Date feedback expired.");
|
|
}
|
|
|
|
if (Odd(int(Data.Order)) || Data.Order == doExecute || Data.Order == doDelete)
|
|
throw ExceptionFrm("Invalid \"order\" value for deal module.");
|
|
|
|
if (Data.Order == doCancel) {
|
|
const CDateTime Until = StringToDate(Data.Payment.Until);
|
|
if (DateTime > Until)
|
|
throw ExceptionFrm("Deal cancellation expired.");
|
|
|
|
CString message(Data.Payment.Address);
|
|
if (!Data.FeedBack.Comments.IsEmpty()) {
|
|
message += LINEFEED;
|
|
message += Data.FeedBack.Comments;
|
|
}
|
|
|
|
if (Data.Seller.Signature.IsEmpty() || !VerifyMessage(message, Data.Seller.Address, Data.Seller.Signature))
|
|
throw ExceptionFrm("The deal is not signed by the seller.");
|
|
}
|
|
|
|
if (Data.Order == doFeedback) {
|
|
CString message(Data.Payment.Address);
|
|
if (!Data.FeedBack.Comments.IsEmpty()) {
|
|
message += LINEFEED;
|
|
message += Data.FeedBack.Comments;
|
|
}
|
|
|
|
if (Data.Customer.Signature.IsEmpty() || !VerifyMessage(message, Data.Customer.Address, Data.Customer.Signature))
|
|
throw ExceptionFrm("The deal is not signed by the customer.");
|
|
}
|
|
|
|
if (!valid_address(Data.Seller.Address))
|
|
throw ExceptionFrm("Invalid Seller address: %s.", Data.Seller.Address.c_str());
|
|
|
|
if (!valid_address(Data.Customer.Address))
|
|
throw ExceptionFrm("Invalid Customer address: %s.", Data.Customer.Address.c_str());
|
|
|
|
if (!valid_address(Data.Payment.Address))
|
|
throw ExceptionFrm("Invalid Payment address: %s.", Data.Payment.Address.c_str());
|
|
}
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
|
|
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& 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);
|
|
|
|
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);
|
|
|
|
Json.Object().AddPair("id", ApostolUID());
|
|
if (!caUserAddress.IsEmpty())
|
|
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 = 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;
|
|
}
|
|
|
|
const auto& caServerKey = m_CurrentServer.PGP().Key;
|
|
|
|
if (caServerKey.IsEmpty())
|
|
throw ExceptionFrm("Server 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::DoAPI(CHTTPServerConnection *AConnection) {
|
|
|
|
auto pRequest = AConnection->Request();
|
|
auto pReply = AConnection->Reply();
|
|
|
|
pReply->ContentType = CHTTPReply::json;
|
|
|
|
CStringList slRouts;
|
|
SplitColumns(pRequest->Location.pathname, slRouts, '/');
|
|
|
|
if (slRouts.Count() < 3) {
|
|
AConnection->SendStockReply(CHTTPReply::not_found);
|
|
return;
|
|
}
|
|
|
|
const auto& caService = slRouts[0].Lower();
|
|
const auto& caVersion = slRouts[1].Lower();
|
|
const auto& caCommand = slRouts[2].Lower();
|
|
const auto& caAction = slRouts.Count() == 4 ? slRouts[3].Lower() : "";
|
|
|
|
if (caService != "api") {
|
|
AConnection->SendStockReply(CHTTPReply::not_found);
|
|
return;
|
|
}
|
|
|
|
if (caVersion != "v1") {
|
|
AConnection->SendStockReply(CHTTPReply::not_found);
|
|
return;
|
|
}
|
|
|
|
CString sRoute;
|
|
for (int i = 0; i < slRouts.Count(); ++i) {
|
|
sRoute.Append('/');
|
|
sRoute.Append(slRouts[i]);
|
|
}
|
|
|
|
try {
|
|
if (caCommand == "ping") {
|
|
|
|
AConnection->SendStockReply(CHTTPReply::ok);
|
|
|
|
} else if (caCommand == "time") {
|
|
|
|
pReply->Content << "{\"serverTime\": " << ToString(MsEpoch()) << "}";
|
|
|
|
AConnection->SendReply(CHTTPReply::ok);
|
|
|
|
} else if (caCommand == "help") {
|
|
|
|
pRequest->Content.Clear();
|
|
|
|
DoAccount(AConnection, "GET", sRoute);
|
|
|
|
} else if (caCommand == "account" && caAction == "status") {
|
|
|
|
pRequest->Content.Clear();
|
|
|
|
DoAccount(AConnection, "GET", sRoute);
|
|
|
|
} else if (caCommand == "deal" && caAction == "status") {
|
|
|
|
pRequest->Content.Clear();
|
|
|
|
DoDeal(AConnection, "GET", sRoute, caAction);
|
|
|
|
} else if (caCommand == "bc" && caAction == "history") {
|
|
|
|
const auto& caAccount = pRequest->Params["account"];
|
|
|
|
if (caAccount.IsEmpty()) {
|
|
AConnection->SendStockReply(CHTTPReply::bad_request);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const wallet::payment_address address(std::string(caAccount.c_str()));
|
|
|
|
CJSON history;
|
|
fetch_history(address, history);
|
|
|
|
pReply->Content = history.ToString();
|
|
} catch (Delphi::Exception::Exception &E) {
|
|
ExceptionToJson(CHTTPReply::bad_request, E, pReply->Content);
|
|
Log()->Error(APP_LOG_EMERG, 0, E.what());
|
|
}
|
|
|
|
AConnection->SendReply(CHTTPReply::ok, nullptr, true);
|
|
|
|
} else if (caCommand == "bc" && caAction == "header") {
|
|
|
|
const auto& caHeight = pRequest->Params["height"];
|
|
const auto& caHash = pRequest->Params["hash"];
|
|
|
|
if (caHeight.IsEmpty() && caHash.IsEmpty()) {
|
|
AConnection->SendStockReply(CHTTPReply::bad_request);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
CJSON header;
|
|
|
|
if (!caHash.IsEmpty()) {
|
|
fetch_header(hash256(std::string(caHash.c_str())), header);
|
|
} else {
|
|
uint32_t height = StrToInt(caHeight.c_str());
|
|
fetch_header(height, header);
|
|
}
|
|
|
|
pReply->Content = header.ToString();
|
|
} catch (Delphi::Exception::Exception &E) {
|
|
ExceptionToJson(CHTTPReply::bad_request, E, pReply->Content);
|
|
Log()->Error(APP_LOG_EMERG, 0, E.what());
|
|
}
|
|
|
|
AConnection->SendReply(CHTTPReply::ok, nullptr, true);
|
|
|
|
} else {
|
|
|
|
AConnection->SendStockReply(CHTTPReply::not_found);
|
|
|
|
}
|
|
|
|
} catch (std::exception &e) {
|
|
CHTTPReply::CStatusType status = CHTTPReply::internal_server_error;
|
|
|
|
ExceptionToJson(0, e, pReply->Content);
|
|
|
|
AConnection->SendReply(status);
|
|
Log()->Error(APP_LOG_EMERG, 0, e.what());
|
|
}
|
|
}
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
|
|
void CWebService::DoGet(CHTTPServerConnection *AConnection) {
|
|
|
|
auto pRequest = AConnection->Request();
|
|
|
|
CString sPath(pRequest->Location.pathname);
|
|
|
|
// Request sPath must be absolute and not contain "..".
|
|
if (sPath.empty() || sPath.front() != '/' || sPath.find(_T("..")) != CString::npos) {
|
|
AConnection->SendStockReply(CHTTPReply::bad_request);
|
|
return;
|
|
}
|
|
|
|
if (sPath.SubString(0, 5) == "/api/") {
|
|
DoAPI(AConnection);
|
|
return;
|
|
}
|
|
|
|
SendResource(AConnection, sPath);
|
|
}
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
|
|
void CWebService::DoPost(CHTTPServerConnection *AConnection) {
|
|
|
|
auto pRequest = AConnection->Request();
|
|
auto pReply = AConnection->Reply();
|
|
|
|
pReply->ContentType = CHTTPReply::json;
|
|
|
|
CStringList slRouts;
|
|
SplitColumns(pRequest->Location.pathname, slRouts, '/');
|
|
|
|
if (slRouts.Count() < 3) {
|
|
AConnection->SendStockReply(CHTTPReply::not_found);
|
|
return;
|
|
}
|
|
|
|
const auto& caService = slRouts[0].Lower();
|
|
const auto& caVersion = slRouts[1].Lower();
|
|
const auto& caCommand = slRouts[2].Lower();
|
|
const auto& caAction = slRouts.Count() == 4 ? slRouts[3].Lower() : "";
|
|
|
|
if (caService != "api") {
|
|
AConnection->SendStockReply(CHTTPReply::not_found);
|
|
return;
|
|
}
|
|
|
|
if (caVersion != "v1") {
|
|
AConnection->SendStockReply(CHTTPReply::not_found);
|
|
return;
|
|
}
|
|
|
|
CString sRoute;
|
|
for (int i = 0; i < slRouts.Count(); ++i) {
|
|
sRoute.Append('/');
|
|
sRoute.Append(slRouts[i]);
|
|
}
|
|
|
|
try {
|
|
|
|
if (caCommand == "account") {
|
|
|
|
DoAccount(AConnection, "POST", sRoute);
|
|
|
|
} else if (caCommand == "deal") {
|
|
|
|
DoDeal(AConnection, "POST", sRoute, caAction);
|
|
|
|
} else if (caCommand == "signature") {
|
|
|
|
DoSignature(AConnection);
|
|
|
|
} else {
|
|
|
|
AConnection->SendStockReply(CHTTPReply::not_found);
|
|
|
|
}
|
|
|
|
} catch (std::exception &e) {
|
|
ExceptionToJson(0, e, pReply->Content);
|
|
|
|
AConnection->SendReply(CHTTPReply::internal_server_error);
|
|
Log()->Error(APP_LOG_EMERG, 0, e.what());
|
|
}
|
|
}
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
|
|
const CContext &CWebService::CurrentServer() const {
|
|
return m_CurrentServer;
|
|
}
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
|
|
bool CWebService::CheckVerifyPGPSignature(int VerifyPGPSignature, CString &Message) {
|
|
if (VerifyPGPSignature == -2) {
|
|
Message = "PGP public key is not meaningful.";
|
|
} else if (VerifyPGPSignature == -1) {
|
|
Message = "PGP signature not valid.";
|
|
} else if (VerifyPGPSignature == 0) {
|
|
Message = "PGP signature not found.";
|
|
} else if (VerifyPGPSignature > 1) {
|
|
Message = "PGP signature status: Unknown.";
|
|
}
|
|
return VerifyPGPSignature == 1;
|
|
}
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
|
|
int CWebService::VerifyPGPSignature(const CString &caClearText, const CString &Key, CString &Message) {
|
|
const OpenPGP::Key signer(Key.c_str());
|
|
const OpenPGP::CleartextSignature cleartext(caClearText.c_str());
|
|
|
|
if (!cleartext.meaningful())
|
|
return -2;
|
|
|
|
const int verified = OpenPGP::Verify::cleartext_signature(signer, cleartext);
|
|
|
|
if (verified) {
|
|
Message = cleartext.get_message().c_str();
|
|
}
|
|
|
|
return verified;
|
|
}
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
|
|
void CWebService::ParsePGPKey(const CString &Key, CStringPairs& ServerList, CStringList& BTCKeys) {
|
|
if (Key.IsEmpty())
|
|
return;
|
|
|
|
const Apostol::PGP::Key pgp(Key.c_str());
|
|
if (!pgp.meaningful())
|
|
return;
|
|
|
|
CStringList Data;
|
|
CPGPUserIdList List;
|
|
CStringList KeyList;
|
|
|
|
pgp.ExportUID(List);
|
|
|
|
for (int i = 0; i < List.Count(); i++) {
|
|
|
|
const auto& uid = List[i];
|
|
|
|
DebugMessage("%s (%s)\n", uid.Name.c_str(), uid.Desc.c_str());
|
|
|
|
const auto& name = uid.Name.Lower();
|
|
const auto& data = uid.Desc.Lower();
|
|
|
|
if (name == "technical_data") {
|
|
SplitColumns(data, Data, ';');
|
|
|
|
if (Data.Count() == 3) {
|
|
m_SyncPeriod = StrToIntDef(Data[1].Trim().c_str(), BPS_DEFAULT_SYNC_PERIOD);
|
|
} else if (Data.Count() == 2) {
|
|
m_SyncPeriod = StrToIntDef(Data[0].Trim().c_str(), BPS_DEFAULT_SYNC_PERIOD);
|
|
} else if (Data.Count() == 1) {
|
|
m_SyncPeriod = StrToIntDef(Data[0].Trim().c_str(), BPS_DEFAULT_SYNC_PERIOD);
|
|
}
|
|
} if (uid.Name.Length() >= 35 && uid.Name.SubString(0, 3) == BM_PREFIX) {
|
|
CStringList urlList;
|
|
if (FindURLInLine(uid.Desc, urlList)) {
|
|
for (int l = 0; l < urlList.Count(); l++) {
|
|
ServerList.AddPair(uid.Name, urlList[l]);
|
|
}
|
|
}
|
|
} else if (name.Find("bitcoin_key") != CString::npos) {
|
|
const auto& key = wallet::ec_public(data.c_str());
|
|
if (verify(key))
|
|
KeyList.AddPair(name, key.encoded());
|
|
}
|
|
}
|
|
|
|
CString Name;
|
|
for (int i = 1; i <= KeyList.Count(); i++) {
|
|
Name = "bitcoin_key";
|
|
Name << i;
|
|
const auto& key = KeyList[Name];
|
|
if (!key.IsEmpty()) {
|
|
BTCKeys.Add(key);
|
|
}
|
|
}
|
|
}
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
|
|
void CWebService::JsonStringToKey(const CString &jsonString, CString& Key) {
|
|
CJSON Json;
|
|
Json << jsonString;
|
|
|
|
if (Json.HasOwnProperty("error")) {
|
|
const auto &error = Json["error"];
|
|
if (error.ValueType() == jvtObject)
|
|
throw Delphi::Exception::Exception(error["message"].AsString().c_str());
|
|
}
|
|
|
|
if (Json.HasOwnProperty("data")) {
|
|
Key = Json["data"].AsString();
|
|
}
|
|
}
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
|
|
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) {
|
|
|
|
Context.PGP().Status = CKeyContext::ksFetching;
|
|
Context.PGP().StatusTime = Now();
|
|
|
|
ARequest->ContentType = CHTTPRequest::json;
|
|
ARequest->Content = CString().Format(R"({"type": "pgp", "account": "%s", "code": "%s", "fields": ["data"]})", Context.Name().c_str(), Context.PGP().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 pReply = pConnection->Reply();
|
|
|
|
DebugReply(pReply);
|
|
|
|
CString Key;
|
|
|
|
try {
|
|
JsonStringToKey(pReply->Content, Key);
|
|
|
|
if (Key.IsEmpty())
|
|
throw Delphi::Exception::Exception("Not found.");
|
|
|
|
Context.PGP().Status = CKeyContext::ksSuccess;
|
|
Context.PGP().StatusTime = Now();
|
|
|
|
Context.PGP().Key = Key;
|
|
|
|
UpdateServerList(Key);
|
|
|
|
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) {
|
|
Log()->Error(APP_LOG_EMERG, 0, "[%s:%d] %s", pClient->Host().c_str(), pClient->Port(), E.what());
|
|
}
|
|
DebugReply(pConnection->Reply());
|
|
}
|
|
|
|
Context.SetStatus(Context::csAuthorized);
|
|
|
|
Context.PGP().Status = CKeyContext::ksError;
|
|
Context.PGP().StatusTime = Now();
|
|
};
|
|
|
|
Context.PGP().Status = CKeyContext::ksUnknown;
|
|
Context.PGP().StatusTime = Now();
|
|
Context.PGP().RunTime = Context.PGP().StatusTime;
|
|
|
|
auto pClient = GetClient(Context.URL().hostname, Context.URL().port == 0 ? BPS_SERVER_PORT : Context.URL().port);
|
|
|
|
pClient->OnRequest(OnRequest);
|
|
pClient->OnExecute(OnExecute);
|
|
pClient->OnException(OnException);
|
|
|
|
pClient->AutoFree(true);
|
|
pClient->Active(true);
|
|
}
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
|
|
void CWebService::ModuleService(CContext &Context) {
|
|
|
|
Log()->Notice("[%s] [%s] Trying to fetch a OAuth2 configuration file.", Context.Name().c_str(), Context.URL().Origin().c_str());
|
|
|
|
auto OnRequest = [this, &Context](CHTTPClient *Sender, CHTTPRequest *ARequest) {
|
|
|
|
Context.SetStatus(Context::csPreparing);
|
|
|
|
ARequest->ContentType = CHTTPRequest::text;
|
|
|
|
Apostol::PGP::CleartextSignature(
|
|
m_pgpModuleKey,
|
|
m_pgpPassphrase,
|
|
BPS_PGP_HASH,
|
|
Context.Name(),
|
|
ARequest->Content);
|
|
|
|
CHTTPRequest::Prepare(ARequest, "POST", "/api/v1/dm/service");
|
|
|
|
const auto &caModuleAddress = m_Module["address"];
|
|
if (!caModuleAddress.IsEmpty())
|
|
ARequest->AddHeader("Module-Address", caModuleAddress);
|
|
|
|
DebugRequest(ARequest);
|
|
};
|
|
|
|
auto OnExecute = [&Context](CTCPConnection *AConnection) {
|
|
|
|
Context.SetStatus(Context::csInitialization);
|
|
|
|
auto pConnection = dynamic_cast<CHTTPClientConnection *> (AConnection);
|
|
|
|
if (pConnection != nullptr) {
|
|
auto pReply = pConnection->Reply();
|
|
|
|
DebugReply(pReply);
|
|
|
|
if (pReply->Status == CHTTPReply::ok) {
|
|
const CJSON OAuth2(pReply->Content);
|
|
UpdateOAuth2(Context, OAuth2.Object());
|
|
}
|
|
|
|
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) {
|
|
Log()->Error(APP_LOG_EMERG, 0, "[%s:%d] %s", pClient->Host().c_str(), pClient->Port(), E.what());
|
|
}
|
|
DebugReply(pConnection->Reply());
|
|
}
|
|
|
|
Context.SetStatus(Context::csInitialization);
|
|
};
|
|
|
|
auto pClient = GetClient(Context.URL().hostname, Context.URL().port == 0 ? BPS_SERVER_PORT : Context.URL().port);
|
|
|
|
pClient->OnRequest(OnRequest);
|
|
pClient->OnExecute(OnExecute);
|
|
pClient->OnException(OnException);
|
|
|
|
pClient->AutoFree(true);
|
|
pClient->Active(true);
|
|
}
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
|
|
void CWebService::ModuleStatus(CContext &Context) {
|
|
|
|
Log()->Notice("[%s] [%s] Trying get module status.", Context.Name().c_str(), Context.URL().Origin().c_str());
|
|
|
|
auto OnRequest = [this, &Context](CHTTPClient *Sender, CHTTPRequest *ARequest) {
|
|
|
|
Context.SetStatus(Context::csInProgress);
|
|
|
|
const auto &caModuleAddress = m_Module["address"];
|
|
|
|
ARequest->ContentType = CHTTPRequest::text;
|
|
|
|
ARequest->Content.Format(R"({"address": "%s"})", caModuleAddress.c_str());
|
|
|
|
CHTTPRequest::Prepare(ARequest, "POST", "/api/v1/dm/status");
|
|
|
|
ARequest->AddHeader("Authorization", "Bearer " + Context.Tokens()["access_token"]);
|
|
|
|
if (!caModuleAddress.IsEmpty())
|
|
ARequest->AddHeader("Module-Address", caModuleAddress);
|
|
|
|
DebugRequest(ARequest);
|
|
};
|
|
|
|
auto OnExecute = [this, &Context](CTCPConnection *AConnection) {
|
|
|
|
auto pConnection = dynamic_cast<CHTTPClientConnection *> (AConnection);
|
|
|
|
if (pConnection != nullptr) {
|
|
auto pReply = pConnection->Reply();
|
|
|
|
DebugReply(pReply);
|
|
|
|
if (pReply->Status == CHTTPReply::ok) {
|
|
ModuleAuthorize(Context);
|
|
} else {
|
|
ModuleNew(Context);
|
|
}
|
|
|
|
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) {
|
|
Log()->Error(APP_LOG_EMERG, 0, "[%s:%d] %s", pClient->Host().c_str(), pClient->Port(), E.what());
|
|
}
|
|
DebugReply(pConnection->Reply());
|
|
}
|
|
|
|
Context.SetStatus(Context::csInitialized);
|
|
};
|
|
|
|
auto pClient = GetClient(Context.URL().hostname, Context.URL().port == 0 ? BPS_SERVER_PORT : Context.URL().port);
|
|
|
|
pClient->OnRequest(OnRequest);
|
|
pClient->OnExecute(OnExecute);
|
|
pClient->OnException(OnException);
|
|
|
|
pClient->AutoFree(true);
|
|
pClient->Active(true);
|
|
}
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
|
|
void CWebService::ModuleNew(CContext &Context) {
|
|
|
|
Log()->Notice("[%s] [%s] Trying create new module.", Context.Name().c_str(), Context.URL().Origin().c_str());
|
|
|
|
auto OnRequest = [this, &Context](CHTTPClient *Sender, CHTTPRequest *ARequest) {
|
|
|
|
Context.SetStatus(Context::csInProgress);
|
|
|
|
ARequest->ContentType = CHTTPRequest::json;
|
|
|
|
const auto &caModuleAddress = m_Module["address"];
|
|
|
|
CJSON Json(jvtObject);
|
|
|
|
Json.Object().AddPair("address", caModuleAddress);
|
|
Json.Object().AddPair("bitmessage", Context.Name());
|
|
|
|
const OpenPGP::SecretKey pgpSecret(m_pgpModuleKey.c_str());
|
|
const auto &public_key = pgpSecret.get_public();
|
|
|
|
Json.Object().AddPair("pgp", public_key.write(OpenPGP::PGP::Armored::YES));
|
|
|
|
ARequest->Content = Json.ToString();
|
|
|
|
CHTTPRequest::Prepare(ARequest, "POST", "/api/v1/client/new");
|
|
|
|
ARequest->AddHeader("Authorization", "Bearer " + Context.Tokens()["access_token"]);
|
|
|
|
if (!caModuleAddress.IsEmpty())
|
|
ARequest->AddHeader("Module-Address", caModuleAddress);
|
|
|
|
DebugRequest(ARequest);
|
|
};
|
|
|
|
auto OnExecute = [this, &Context](CTCPConnection *AConnection) {
|
|
|
|
Context.SetStatus(Context::csInitialized);
|
|
|
|
auto pConnection = dynamic_cast<CHTTPClientConnection *> (AConnection);
|
|
|
|
if (pConnection != nullptr) {
|
|
auto pReply = pConnection->Reply();
|
|
|
|
DebugReply(pReply);
|
|
|
|
if (pReply->Status == CHTTPReply::ok) {
|
|
const CJSON Json(pReply->Content);
|
|
|
|
if (Json.HasOwnProperty("result")) {
|
|
const auto &caResult = Json["result"];
|
|
if (caResult.HasOwnProperty("success")) {
|
|
if (caResult["success"].AsBoolean()) {
|
|
ModuleAuthorize(Context);
|
|
} else {
|
|
if (caResult.HasOwnProperty("message")) {
|
|
Log()->Error(APP_LOG_EMERG, 0, "[%s] [%s] %s", Context.Name().c_str(),
|
|
Context.URL().Origin().c_str(), caResult["message"].AsString().c_str());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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) {
|
|
Log()->Error(APP_LOG_EMERG, 0, "[%s:%d] %s", pClient->Host().c_str(), pClient->Port(), E.what());
|
|
}
|
|
DebugReply(pConnection->Reply());
|
|
}
|
|
|
|
Context.SetStatus(Context::csInitialized);
|
|
};
|
|
|
|
auto pClient = GetClient(Context.URL().hostname, Context.URL().port == 0 ? BPS_SERVER_PORT : Context.URL().port);
|
|
|
|
pClient->OnRequest(OnRequest);
|
|
pClient->OnExecute(OnExecute);
|
|
pClient->OnException(OnException);
|
|
|
|
pClient->AutoFree(true);
|
|
pClient->Active(true);
|
|
}
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
|
|
void CWebService::ModuleAuthorize(CContext &Context) {
|
|
|
|
Log()->Notice("[%s] [%s] Trying authorize module.", Context.Name().c_str(), Context.URL().Origin().c_str());
|
|
|
|
auto OnRequest = [this, &Context](CHTTPClient *Sender, CHTTPRequest *ARequest) {
|
|
|
|
Context.SetStatus(Context::csAuthorization);
|
|
|
|
ARequest->ContentType = CHTTPRequest::text;
|
|
|
|
Apostol::PGP::CleartextSignature(
|
|
m_pgpModuleKey,
|
|
m_pgpPassphrase,
|
|
BPS_PGP_HASH,
|
|
Context.Name(),
|
|
ARequest->Content);
|
|
|
|
CHTTPRequest::Prepare(ARequest, "POST", "/api/v1/dm/authorize");
|
|
|
|
ARequest->AddHeader("Authorization", "Bearer " + Context.Tokens()["access_token"]);
|
|
|
|
const auto &caModuleAddress = m_Module["address"];
|
|
if (!caModuleAddress.IsEmpty())
|
|
ARequest->AddHeader("Module-Address", caModuleAddress);
|
|
|
|
DebugRequest(ARequest);
|
|
};
|
|
|
|
auto OnExecute = [&Context](CTCPConnection *AConnection) {
|
|
|
|
Context.SetStatus(Context::csInitialized);
|
|
|
|
auto pConnection = dynamic_cast<CHTTPClientConnection *> (AConnection);
|
|
|
|
if (pConnection != nullptr) {
|
|
auto pReply = pConnection->Reply();
|
|
|
|
DebugReply(pReply);
|
|
|
|
if (pReply->Status == CHTTPReply::ok) {
|
|
const CJSON Json(pReply->Content);
|
|
|
|
Context.Session() = Json["session"].AsString();
|
|
Context.Secret() = Json["secret"].AsString();
|
|
|
|
Context.Tokens().Values("access_token", Json["access_token"].AsString());
|
|
|
|
Context.SetFixedDate(0);
|
|
Context.SetCheckDate(Now() + (CDateTime) 55 / MinsPerDay); // 55 min
|
|
|
|
Context.SetStatus(csAuthorized);
|
|
}
|
|
|
|
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) {
|
|
Log()->Error(APP_LOG_EMERG, 0, "[%s:%d] %s", pClient->Host().c_str(), pClient->Port(), E.what());
|
|
}
|
|
DebugReply(pConnection->Reply());
|
|
}
|
|
|
|
Context.SetStatus(Context::csInitialized);
|
|
};
|
|
|
|
auto pClient = GetClient(Context.URL().hostname, Context.URL().port == 0 ? BPS_SERVER_PORT : Context.URL().port);
|
|
|
|
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_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();
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
|
|
void CWebService::Initialization(CModuleProcess *AProcess) {
|
|
CApostolModule::Initialization(AProcess);
|
|
|
|
if (Enabled()) {
|
|
Reload();
|
|
}
|
|
}
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
|
|
bool CWebService::Enabled() {
|
|
if (m_ModuleStatus == msUnknown)
|
|
m_ModuleStatus = Config()->IniFile().ReadBool(SectionName().c_str(), "enable", true) ? msEnabled : msDisabled;
|
|
return m_ModuleStatus == msEnabled;
|
|
}
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
|
|
CString CWebService::ToString(unsigned long Value) {
|
|
TCHAR szString[_INT_T_LEN + 1] = {0};
|
|
sprintf(szString, "%lu", Value);
|
|
return { szString };
|
|
}
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
|
|
}
|
|
}
|
|
} |