From fd24410ee4fa3f5609c7529490a58dcea3d3f26a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=BF=D0=BE=D0=B4=D0=BE=D0=B1=D0=BD?= =?UTF-8?q?=D1=8B=D0=B9=20=D0=90=D0=BB=D0=B5=D0=BD?= Date: Sun, 3 Jul 2022 18:01:40 +0300 Subject: [PATCH] Committing updates. --- src/modules/Workers/WebService/WebService.cpp | 33 +- src/modules/Workers/WebService/WebService.hpp | 4 +- src/modules/Workers/WebSocket/WebSocket.cpp | 925 +++++++++++++++++- src/modules/Workers/WebSocket/WebSocket.hpp | 9 +- .../Workers/WebSocket/WebSocketClient.cpp | 2 +- .../Workers/WebSocket/WebSocketClient.hpp | 5 +- 6 files changed, 928 insertions(+), 50 deletions(-) diff --git a/src/modules/Workers/WebService/WebService.cpp b/src/modules/Workers/WebService/WebService.cpp index 53722c4..62ae9bb 100644 --- a/src/modules/Workers/WebService/WebService.cpp +++ b/src/modules/Workers/WebService/WebService.cpp @@ -42,14 +42,6 @@ namespace Apostol { namespace Module { - CString to_string(unsigned long Value) { - TCHAR szString[_INT_T_LEN + 1] = {0}; - sprintf(szString, "%lu", Value); - return {szString}; - } - //-------------------------------------------------------------------------------------------------------------- - - //-------------------------------------------------------------------------------------------------------------- //-- CWebService ----------------------------------------------------------------------------------------------- @@ -487,7 +479,7 @@ namespace Apostol { } //-------------------------------------------------------------------------------------------------------------- - void CWebService::DoUser(CHTTPServerConnection *AConnection, const CString& Method, const CString& URI) { + void CWebService::DoAccount(CHTTPServerConnection *AConnection, const CString& Method, const CString& URI) { auto pProxy = GetProxy(AConnection); auto pServerRequest = AConnection->Request(); @@ -679,8 +671,8 @@ namespace Apostol { } } - //DebugMessage("[DoUser] Server request:\n%s\n", pServerRequest->Content.c_str()); - //DebugMessage("[DoUser] sPayload:\n%s\n", sPayload.c_str()); + //DebugMessage("[DoAccount] Server request:\n%s\n", pServerRequest->Content.c_str()); + //DebugMessage("[DoAccount] sPayload:\n%s\n", sPayload.c_str()); CJSON Json(jvtObject); @@ -718,6 +710,7 @@ namespace Apostol { const auto DateTime = UTC(); const auto Date = StringToDate(Data.Date); + const auto Sum = BTCToDouble(Data.Payment.Sum); if (Data.Order == doCreate) { if (DateTime < Date) @@ -1135,9 +1128,6 @@ namespace Apostol { } } - //DebugMessage("[DoDeal] Server request:\n%s\n", pServerRequest->Content.c_str()); - //DebugMessage("[DoDeal] sPayload:\n%s\n", sPayload.c_str()); - CJSON Json(jvtObject); Json.Object().AddPair("id", ApostolUID()); @@ -1277,7 +1267,7 @@ namespace Apostol { } else if (caCommand == "time") { - pReply->Content << "{\"serverTime\": " << to_string(MsEpoch()) << "}"; + pReply->Content << "{\"serverTime\": " << ToString(MsEpoch()) << "}"; AConnection->SendReply(CHTTPReply::ok); @@ -1285,13 +1275,13 @@ namespace Apostol { pRequest->Content.Clear(); - DoUser(AConnection, "GET", sRoute); + DoAccount(AConnection, "GET", sRoute); } else if (caCommand == "account" && caAction == "status") { pRequest->Content.Clear(); - DoUser(AConnection, "GET", sRoute); + DoAccount(AConnection, "GET", sRoute); } else if (caCommand == "deal" && caAction == "status") { @@ -1428,7 +1418,7 @@ namespace Apostol { if (caCommand == "account") { - DoUser(AConnection, "POST", sRoute); + DoAccount(AConnection, "POST", sRoute); } else if (caCommand == "deal") { @@ -1709,6 +1699,13 @@ namespace Apostol { } //-------------------------------------------------------------------------------------------------------------- + CString CWebService::ToString(unsigned long Value) { + TCHAR szString[_INT_T_LEN + 1] = {0}; + sprintf(szString, "%lu", Value); + return { szString }; + } + //-------------------------------------------------------------------------------------------------------------- + } } } \ No newline at end of file diff --git a/src/modules/Workers/WebService/WebService.hpp b/src/modules/Workers/WebService/WebService.hpp index 83aa062..a496097 100644 --- a/src/modules/Workers/WebService/WebService.hpp +++ b/src/modules/Workers/WebService/WebService.hpp @@ -81,7 +81,7 @@ namespace Apostol { static void CheckKeyForNull(LPCTSTR Key, LPCTSTR Value); - void DoUser(CHTTPServerConnection *AConnection, const CString &Method, const CString &URI); + void DoAccount(CHTTPServerConnection *AConnection, const CString &Method, const CString &URI); void DoDeal(CHTTPServerConnection *AConnection, const CString &Method, const CString &URI, const CString &Action); void DoSignature(CHTTPServerConnection *AConnection); @@ -114,6 +114,8 @@ namespace Apostol { void Reload(); + static CString ToString(unsigned long Value); + static void JsonStringToKey(const CString& jsonString, CString& Key); static void CheckDeal(const CDeal& Deal); diff --git a/src/modules/Workers/WebSocket/WebSocket.cpp b/src/modules/Workers/WebSocket/WebSocket.cpp index 00547a7..3b1ce61 100644 --- a/src/modules/Workers/WebSocket/WebSocket.cpp +++ b/src/modules/Workers/WebSocket/WebSocket.cpp @@ -106,6 +106,7 @@ namespace Apostol { void CWebSocketModule::UpdateServerList(const CString &Key) { CStringPairs ServerList; CStringList Keys; + int index; ParsePGPKey(Key, ServerList, Keys); @@ -113,14 +114,15 @@ namespace Apostol { CStringPairs::ConstEnumerator em(ServerList); while (em.MoveNext()) { const auto ¤t = em.Current(); - if (m_Servers.IndexOfName(current.Name()) == -1) { - m_Servers.AddPair(current.Name(), CClientContext(CLocation(current.Value()))); - auto &Context = m_Servers.Last().Value(); - Context.Name() = current.Name(); - Context.PGP().Name = "PUBLIC"; - Context.PGP().Key = Key; - Context.BTCKeys() = Keys; + index = m_Servers.IndexOfName(current.Name()); + if (index == -1) { + index = m_Servers.AddPair(current.Name(), CClientContext(CLocation(current.Value()))); } + auto &Context = m_Servers[index].Value(); + Context.Name() = current.Name(); + Context.PGP().Name = "PUBLIC"; + Context.PGP().Key = Key; + Context.BTCKeys() = Keys; } } } @@ -136,8 +138,8 @@ namespace Apostol { if (!oauth2.empty() && Context.Status() == Context::csInitialization) { LoadOAuth2(oauth2, provider, application, Context.Providers()); Context.SetStatus(Context::csInitialized); + Context.SetCheckDate(0); } - Context.SetCheckDate(0); } } //-------------------------------------------------------------------------------------------------------------- @@ -225,7 +227,7 @@ namespace Apostol { //-------------------------------------------------------------------------------------------------------------- CWebSocketClient *CWebSocketModule::GetWebSocketClient(CClientContext &Context) { - auto pClient = Context.ClientManager().Add(&Context, CLocation(Context.URL().Origin() + "/module/" + Context.Session())); + auto pClient = Context.ClientManager().Add(&Context, CLocation(Context.URL().Origin() + "/session/" + Context.Session())); pClient->Session() = Context.Session(); @@ -276,7 +278,9 @@ namespace Apostol { pClient->Active(true); Context.SetFixedDate(0); - Context.SetStatus(csRunning); + + if (Context.Status() == Context::csInProgress) + Context.SetStatus(csRunning); } catch (std::exception &e) { Log()->Error(APP_LOG_ERR, 0, e.what()); } @@ -354,7 +358,8 @@ namespace Apostol { if (pReply->Status == CHTTPReply::ok) { const CJSON Json(pReply->Content); - Context.SetStatus(csAuthorized); + if (Context.Status() == Context::csAuthorization) + Context.SetStatus(csAuthorized); Context.Session() = Json["session"].AsString(); Context.Secret() = Json["secret"].AsString(); @@ -552,13 +557,650 @@ namespace Apostol { } //-------------------------------------------------------------------------------------------------------------- - void CWebSocketModule::DoUser(CHTTPServerConnection *AConnection, const CString &Method, const CString &URI) { + void CWebSocketModule::DoAccount(CHTTPServerConnection *AConnection, const CString &Method, const CString &URI) { + auto OnRequest = [AConnection](CWebSocketMessageHandler *AHandler, CWebSocketConnection *AWSConnection) { + auto pReply = AConnection->Reply(); + const auto &wsMessage = CCustomWebSocketClient::RequestToMessage(AWSConnection); + pReply->ContentType = CHTTPReply::json; + if (wsMessage.MessageTypeId == mtCallResult) { + pReply->Content = wsMessage.Payload.ToString(); + AConnection->SendReply(CHTTPReply::ok, nullptr, true); + } else { + ReplyError(AConnection, CHTTPReply::bad_request, wsMessage.ErrorMessage); + } + }; + + auto pServerRequest = AConnection->Request(); + + const auto& caModuleAddress = m_Module["address"]; + const auto& caHost = pServerRequest->Headers["host"]; + const auto& caOrigin = pServerRequest->Headers["origin"]; + const auto& caUserAddress = pServerRequest->Params["address"]; + + const auto& pgpValue = pServerRequest->Params["pgp"]; + const auto& caServerParam = pServerRequest->Params["server"]; + + 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; + } + + const auto& caPGPPrivateFile = Config()->IniFile().ReadString("pgp", "private", ""); + const auto& caPGPPassphrase = Config()->IniFile().ReadString("pgp", "passphrase", ""); + + if (!FileExists(caPGPPrivateFile.c_str())) + throw Delphi::Exception::Exception("PGP: Private key file not opened."); + + CString sPGPPrivate; + sPGPPrivate.LoadFromFile(caPGPPrivateFile.c_str()); + + if (pgpValue == "off" || pgpValue == "false") { + sPayload = caClearText.Text(); + } else { + Apostol::PGP::CleartextSignature( + sPGPPrivate, + caPGPPassphrase, + 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)); + + int index = 0; + while (index < m_Servers.Count()) { + const auto &Context = m_Servers[index].Value(); + if (Context.Status() == Context::csRunning) { + if (caServerParam.IsEmpty()) { + break; + } else { + if (Context.URL().Origin() == caServerParam) + break; + } + } + } + + if (index == m_Servers.Count()) { + throw Delphi::Exception::Exception("Not found active connection."); + } + + const auto &caContext = m_Servers[index].Value(); + + auto pClient = caContext.ClientManager().First(); + + pClient->Send(URI, Json, OnRequest); } //-------------------------------------------------------------------------------------------------------------- void CWebSocketModule::DoDeal(CHTTPServerConnection *AConnection, const CString &Method, const CString &URI, const CString &Action) { + auto OnRequest = [AConnection](CWebSocketMessageHandler *AHandler, CWebSocketConnection *AWSConnection) { + auto pReply = AConnection->Reply(); + const auto &wsMessage = CCustomWebSocketClient::RequestToMessage(AWSConnection); + pReply->ContentType = CHTTPReply::json; + if (wsMessage.MessageTypeId == mtCallResult) { + pReply->Content = wsMessage.Payload.ToString(); + AConnection->SendReply(CHTTPReply::ok, nullptr, true); + } else { + ReplyError(AConnection, CHTTPReply::bad_request, wsMessage.ErrorMessage); + } + }; + + auto pServerRequest = AConnection->Request(); + + const auto& caModuleAddress = m_Module["address"]; + const auto& caModuleFee = m_Module["fee"]; + + const auto checkFee = CheckFee(caModuleFee); + if (checkFee == -1) + throw ExceptionFrm("Invalid module fee value: %s", caModuleFee.c_str()); + + const auto& caHost = pServerRequest->Headers["host"]; + const auto& caOrigin = pServerRequest->Headers["origin"]; + const auto& caUserAddress = pServerRequest->Params["address"]; + const auto& pgpValue = pServerRequest->Params["pgp"]; + const auto& caServerParam = pServerRequest->Params["server"]; + + int index = 0; + while (index < m_Servers.Count()) { + const auto &Context = m_Servers[index].Value(); + if (Context.Status() == Context::csRunning) { + if (caServerParam.IsEmpty()) { + break; + } else { + if (Context.URL().Origin() == caServerParam) + break; + } + } + } + + if (index == m_Servers.Count()) { + throw Delphi::Exception::Exception("Not found active connection."); + } + + const auto &caContext = m_Servers[index].Value(); + + 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); + CheckKeyForNull("type", formType); + CheckKeyForNull("at", formAt); + CheckKeyForNull("date", formDate); + CheckKeyForNull("seller.address", formSellerAddress); + CheckKeyForNull("customer.address", formCustomerAddress); + CheckKeyForNull("payment.sum", formPaymentSum); + + if (action == "cancel") { + CheckKeyForNull("seller.signature", formSellerSignature); + } + + if (action == "feedback") { + CheckKeyForNull("customer.signature", formCustomerSignature); + } + + 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 = 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) { + 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 auto& caPGPPrivateFile = Config()->IniFile().ReadString("pgp", "private", ""); + const auto& caPGPPassphrase = Config()->IniFile().ReadString("pgp", "passphrase", ""); + + if (!FileExists(caPGPPrivateFile.c_str())) + throw Delphi::Exception::Exception("PGP: Private key file not opened."); + + CString sPGPPrivate; + sPGPPrivate.LoadFromFile(caPGPPrivateFile.c_str()); + + const CString caClearText(YAML::Dump(Node)); + + if (pgpValue == "off" || pgpValue == "false") { + sPayload = caClearText; + } else { + Apostol::PGP::CleartextSignature( + sPGPPrivate, + caPGPPassphrase, + 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 (!sPayload.IsEmpty()) + Json.Object().AddPair("payload", base64_encode(sPayload)); + + auto pClient = caContext.ClientManager().First(); + + pClient->Send(URI, Json, OnRequest); } //-------------------------------------------------------------------------------------------------------------- @@ -568,7 +1210,134 @@ namespace Apostol { //-------------------------------------------------------------------------------------------------------------- void CWebSocketModule::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()); + } } //-------------------------------------------------------------------------------------------------------------- @@ -630,30 +1399,17 @@ namespace Apostol { } try { - if (caCommand == "account") { - - DoUser(AConnection, "POST", sRoute); - + 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()); + ReplyError(AConnection, CHTTPReply::bad_request, e.what()); } } //-------------------------------------------------------------------------------------------------------------- @@ -664,7 +1420,9 @@ namespace Apostol { if ((Now >= Context.CheckDate()) && (Context.Status() >= Context::csInitialized)) { Context.SetCheckDate(Now + (CDateTime) 30 / SecsPerDay); // 30 sec - Context.SetStatus(Context::csAuthorization); + + if (Context.Status() == Context::csInitialized) + Context.SetStatus(Context::csAuthorization); CheckProviders(Now, Context); FetchProviders(Now, Context); @@ -700,6 +1458,8 @@ namespace Apostol { //-------------------------------------------------------------------------------------------------------------- void CWebSocketModule::Reload() { + Config()->IniFile().ReadSectionValues("module", &m_Module); + const auto& caPublicKey = Config()->IniFile().ReadString("pgp", "public", "dm.pub"); if (FileExists(caPublicKey.c_str())) { @@ -720,6 +1480,115 @@ namespace Apostol { return m_ModuleStatus == msEnabled; } //-------------------------------------------------------------------------------------------------------------- + + int CWebSocketModule::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 CWebSocketModule::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 CWebSocketModule::CheckKeyForNull(LPCTSTR key, const CString &Value) { + if (Value.IsEmpty()) + throw ExceptionFrm("Invalid format: key \"%s\" cannot be empty.", key); + } + //-------------------------------------------------------------------------------------------------------------- + + CString CWebSocketModule::ToString(unsigned long Value) { + TCHAR szString[_INT_T_LEN + 1] = {0}; + sprintf(szString, "%lu", Value); + return { szString }; + } + //-------------------------------------------------------------------------------------------------------------- + } } } \ No newline at end of file diff --git a/src/modules/Workers/WebSocket/WebSocket.hpp b/src/modules/Workers/WebSocket/WebSocket.hpp index eb491c9..5c88ffd 100644 --- a/src/modules/Workers/WebSocket/WebSocket.hpp +++ b/src/modules/Workers/WebSocket/WebSocket.hpp @@ -67,6 +67,8 @@ namespace Apostol { class CWebSocketModule: public CApostolModule { private: + CStringList m_Module; + int m_SyncPeriod; CClientContextPair m_DefaultServer { BPS_BM_SERVER_ADDRESS, CClientContext(CLocation(BPS_SERVER_URL)) }; @@ -92,7 +94,7 @@ namespace Apostol { void Heartbeat(CDateTime Now) override; - void DoUser(CHTTPServerConnection *AConnection, const CString &Method, const CString &URI); + void DoAccount(CHTTPServerConnection *AConnection, const CString &Method, const CString &URI); void DoDeal(CHTTPServerConnection *AConnection, const CString &Method, const CString &URI, const CString &Action); void DoSignature(CHTTPServerConnection *AConnection); @@ -132,6 +134,11 @@ namespace Apostol { bool Enabled() override; + static CString ToString(unsigned long Value); + static int CheckFee(const CString& Fee); + static void CheckDeal(const CDeal& Deal); + static void CheckKeyForNull(LPCTSTR key, const CString& Value); + static bool FindURLInLine(const CString &Line, CStringList &List); static void LoadOAuth2(const CString &FileName, const CString &ProviderName, const CString &ApplicationName, CProviders &Providers); }; diff --git a/src/modules/Workers/WebSocket/WebSocketClient.cpp b/src/modules/Workers/WebSocket/WebSocketClient.cpp index da2b809..4315c77 100644 --- a/src/modules/Workers/WebSocket/WebSocketClient.cpp +++ b/src/modules/Workers/WebSocket/WebSocketClient.cpp @@ -170,7 +170,7 @@ namespace Apostol { } //-------------------------------------------------------------------------------------------------------------- - void CWebSocketClient::Send(const CString &Action, const CString &Payload, COnMessageHandlerEvent &&Handler) { + void CWebSocketClient::Send(const CString &Action, const CJSON &Payload, COnMessageHandlerEvent &&Handler) { auto OnRequest = [this](CWebSocketMessageHandler *AHandler, CWebSocketConnection *AWSConnection) { const auto &wsMessage = RequestToMessage(AWSConnection); diff --git a/src/modules/Workers/WebSocket/WebSocketClient.hpp b/src/modules/Workers/WebSocket/WebSocketClient.hpp index 77ff7dc..747bdd7 100644 --- a/src/modules/Workers/WebSocket/WebSocketClient.hpp +++ b/src/modules/Workers/WebSocket/WebSocketClient.hpp @@ -90,7 +90,7 @@ namespace Apostol { void Subscribe(); void UpdateKey(); - void Send(const CString &Action, const CString &Payload, COnMessageHandlerEvent &&Handler = nullptr); + void Send(const CString &Action, const CJSON &Payload, COnMessageHandlerEvent &&Handler = nullptr); void Reload(); @@ -151,6 +151,9 @@ namespace Apostol { CWebSocketClientItem *Add(CContext *Context, const CLocation &URI); + CWebSocketClientItem *First() const { return GetItem(0); }; + CWebSocketClientItem *Last() const { return GetItem(GetCount() - 1); }; + CWebSocketClientItem *Items(int Index) const override { return GetItem(Index); }; CWebSocketClientItem *operator[] (int Index) const override { return Items(Index); };