From cfea1fe3e902d3f2bd48657d07a0f8d1dce29e1f Mon Sep 17 00:00:00 2001 From: Yevhen Viter Date: Mon, 18 Oct 2021 14:42:52 +0300 Subject: [PATCH] Initial commit --- .../peerplays_sidechain/CMakeLists.txt | 1 + .../peerplays_sidechain/common/https_call.cpp | 247 ++++++++++++++++++ .../peerplays_sidechain/common/https_call.h | 84 ++++++ .../peerplays_sidechain/common/rpc_client.cpp | 29 ++ 4 files changed, 361 insertions(+) create mode 100644 libraries/plugins/peerplays_sidechain/common/https_call.cpp create mode 100644 libraries/plugins/peerplays_sidechain/common/https_call.h diff --git a/libraries/plugins/peerplays_sidechain/CMakeLists.txt b/libraries/plugins/peerplays_sidechain/CMakeLists.txt index 48dd44f1..70e25460 100755 --- a/libraries/plugins/peerplays_sidechain/CMakeLists.txt +++ b/libraries/plugins/peerplays_sidechain/CMakeLists.txt @@ -15,6 +15,7 @@ add_library( peerplays_sidechain bitcoin/utils.cpp bitcoin/sign_bitcoin_transaction.cpp common/rpc_client.cpp + common/https_call.cpp common/utils.cpp hive/asset.cpp hive/operations.cpp diff --git a/libraries/plugins/peerplays_sidechain/common/https_call.cpp b/libraries/plugins/peerplays_sidechain/common/https_call.cpp new file mode 100644 index 00000000..40078501 --- /dev/null +++ b/libraries/plugins/peerplays_sidechain/common/https_call.cpp @@ -0,0 +1,247 @@ +#include "https_call.h" + +#include +#include +#include + +#include +#include + + +namespace peerplays { +namespace net { + +namespace detail { + +static const char Cr = 0x0D; +static const char Lf = 0x0A; +static const char * CrLf = "\x0D\x0A"; +static const char * CrLfCrLf = "\x0D\x0A\x0D\x0A"; + +using namespace boost::asio; + +[[noreturn]] static void throwError(const std::string & msg) { + throw std::runtime_error(msg); +} + +class Impl { +public: + + Impl(const HttpsCall & call, const HttpRequest & request, HttpResponse & response) : + m_Call(call), + m_Request(request), + m_Response(response), + m_Service(), + m_Context(ssl::context::tlsv12), + m_Socket(m_Service, m_Context), + m_Endpoint(), + m_ResponseBuf(call.responseSizeLimitBytes()), + m_ContentLength(0) + {} + + void exec() { + resolve(); + connect(); + sendRequest(); + processResponse(); + } + +private: + + const HttpsCall & m_Call; + const HttpRequest & m_Request; + HttpResponse & m_Response; + + io_service m_Service; + ssl::context m_Context; + ssl::stream m_Socket; + ip::tcp::endpoint m_Endpoint; + streambuf m_ResponseBuf; + uint32_t m_ContentLength; + + static uint16_t u16Swap(uint16_t x) { + return ((x >> 8) & 0x00FF) | ((x << 8) & 0xFF00); + } + + void resolve() { + + // resolve TCP endpoint for host name + + ip::tcp::resolver resolver(m_Service); + auto query = ip::tcp::resolver::query(m_Call.host(), "https"); + auto iter = resolver.resolve(query); + m_Endpoint = *iter; + + if (m_Call.port() != 0) // if port was specified + m_Endpoint.port(u16Swap(m_Call.port())); // force set port + } + + void connect() { + + // TCP connect + + m_Socket.lowest_layer().connect(m_Endpoint); + + // SSL connect + + m_Socket.set_verify_mode(ssl::verify_none); + m_Socket.handshake(ssl::stream_base::client); + } + + void sendRequest() { + + streambuf requestBuf; + std::ostream stream(&requestBuf); + + // start string: method path HTTP/1.0 + + stream << m_Request.method << " " << m_Request.path << " HTTP/1.0" << CrLf; + + // host + + stream << "Host: " << m_Call.host() << ":" << u16Swap(m_Endpoint.port()) << CrLf; + + // content + + if (!m_Request.body.empty()) { + stream << "Content-Type: " << m_Request.contentType << CrLf; + stream << "Content-Length: " << m_Request.body.size() << CrLf; + } + + // additional headers + + const auto & h = m_Request.headers; + + if (!h.empty()) { + if (h.size() < 2) + throw 1; + stream << h; + if ((h.substr(h.size() - 2) != CrLf)) + stream << CrLf; + } + + // other + + stream << "Accept: *\x2F*" << CrLf; + stream << "Connection: close" << CrLf; + + // end + + stream << CrLf; + + // content + + if (!m_Request.body.empty()) + stream << m_Request.body; + + // send + + write(m_Socket, requestBuf); + } + + void helper1() { + + std::istream stream(&m_ResponseBuf); + + std::string httpVersion; + stream >> httpVersion; + stream >> m_Response.statusCode; + + if (!stream || httpVersion.substr(0, 5) != "HTTP/") { + throw "invalid response"; + } + + // read/skip headers + + for(;;) { + std::string header; + if (!std::getline(stream, header, Lf) || (header.size() == 1 && header[0] == Cr)) + break; + if (m_ContentLength) // if content length is already known + continue; // continue skipping headers + auto pos = header.find(':'); + if (pos == std::string::npos) + continue; + auto name = header.substr(0, pos); + boost::algorithm::trim(name); + boost::algorithm::to_lower(name); + if (name != "content-length") + continue; + auto value = header.substr(pos + 1); + boost::algorithm::trim(value); + m_ContentLength = std::stol(value); + } + + } + + void processResponse() { + + auto & socket = m_Socket; + auto & buf = m_ResponseBuf; + auto & contentLength = m_ContentLength; + auto & body = m_Response.body; + + read_until(socket, buf, CrLfCrLf); + + helper1(); + + if (contentLength < 2) // minimum content is "{}" + throwError("invalid response body (too short)"); + + if (contentLength > m_Call.responseSizeLimitBytes()) + throwError("response body size limit exceeded"); + + auto avail = buf.size(); + + if (avail > contentLength) + throwError("invalid response body (content length mismatch)"); + + body.resize(contentLength); + + if (avail) { + if (avail != buf.sgetn(&body[0], avail)) { + throwError("stream read failed"); + } + } + + auto rest = contentLength - avail; + + boost::system::error_code errorCode; + + read(socket, buffer(&body[avail], rest), errorCode); + + socket.shutdown(errorCode); + + } + +}; + +} // detail + +// HttpsCall + +HttpsCall::HttpsCall(const std::string & host, uint16_t port) : + m_Host(host), + m_Port(port) +{} + +bool HttpsCall::exec(const HttpRequest & request, HttpResponse * response) { + +// ASSERT(response); + auto & resp = *response; + + detail::Impl impl(*this, request, resp); + + try { + resp.clear(); + impl.exec(); + } catch (...) { + resp.clear(); + return false; + } + + return true; +} + +} // net +} // peerplays diff --git a/libraries/plugins/peerplays_sidechain/common/https_call.h b/libraries/plugins/peerplays_sidechain/common/https_call.h new file mode 100644 index 00000000..5a96d481 --- /dev/null +++ b/libraries/plugins/peerplays_sidechain/common/https_call.h @@ -0,0 +1,84 @@ +#pragma once + +#include + +namespace peerplays { +namespace net { + +struct HttpRequest { + + std::string method; // ex: "POST" + std::string path; // ex: "/" + std::string headers; + std::string body; + std::string contentType; // ex: "application/json" + + HttpRequest() {} + + HttpRequest(const std::string & method_, const std::string & path_, const std::string & headers_, const std::string & body_, const std::string & contentType_) : + method(method_), + path(path_), + headers(headers_), + body(body_), + contentType(contentType_) + {} + + HttpRequest(const std::string & method_, const std::string & path_, const std::string & headers_, const std::string & body_ = std::string()) : + method(method_), + path(path_), + headers(headers_), + body(body_), + contentType("application/json") + {} + + void clear() { + method.clear(); + path.clear(); + headers.clear(); + body.clear(); + contentType.clear(); + } + +}; + +struct HttpResponse { + + uint16_t statusCode; + std::string body; + + void clear() { + statusCode = 0; + body = decltype(body)(); + } + +}; + +class HttpsCall { +public: + + static constexpr auto ResponseSizeLimitBytes = 1024 * 1024; + + HttpsCall(const std::string & host, uint16_t port = 0); + + bool exec(const HttpRequest & request, HttpResponse * response); + + const std::string host() const { + return m_Host; + } + + uint16_t port() const { + return m_Port; + } + + uint32_t responseSizeLimitBytes() const { + return ResponseSizeLimitBytes; + } + +private: + std::string m_Host; + uint16_t m_Port; +}; + + +} // net +} // peerplays diff --git a/libraries/plugins/peerplays_sidechain/common/rpc_client.cpp b/libraries/plugins/peerplays_sidechain/common/rpc_client.cpp index cb7c5251..b3d9c4e5 100644 --- a/libraries/plugins/peerplays_sidechain/common/rpc_client.cpp +++ b/libraries/plugins/peerplays_sidechain/common/rpc_client.cpp @@ -9,6 +9,8 @@ #include #include +#include "https_call.h" + namespace graphene { namespace peerplays_sidechain { rpc_client::rpc_client(std::string _ip, uint32_t _port, std::string _user, std::string _password, bool _debug_rpc_calls) : @@ -100,6 +102,33 @@ std::string rpc_client::send_post_request(std::string method, std::string params } fc::http::reply rpc_client::send_post_request(std::string body, bool show_log) { + + using namespace peerplays::net; + + HttpRequest request("POST", "/", authorization.key + ":" + authorization.val, body); + + HttpsCall call(ip, port); + + HttpRequest response; + + fc::http::reply reply; + + if (call.exec(request, &response)) { + reply.status = response.statusCode; + reply.body.resize(response.body.size()); + memcpy(&reply.body[0], &response.body[0], response.body.size()); + } + + if (show_log) { + std::string url = "http://" + ip + ":" + std::to_string(port); + ilog("### Request URL: ${url}", ("url", url)); + ilog("### Request: ${body}", ("body", body)); + std::stringstream ss(std::string(reply.body.begin(), reply.body.end())); + ilog("### Response: ${ss}", ("ss", ss.str())); + } + + return reply; + fc::http::connection conn; conn.connect_to(fc::ip::endpoint(fc::ip::address(ip), port));