From 9212c28ef8dcbe6baf9a7e8a68d43d9662cb4013 Mon Sep 17 00:00:00 2001 From: Miguel Portilla Date: Fri, 18 May 2018 09:28:33 -0400 Subject: [PATCH] Add HTTPS file downloader client --- src/ripple/net/SSLHTTPDownloader.h | 85 ++++++++ src/ripple/net/impl/SSLHTTPDownloader.cpp | 251 ++++++++++++++++++++++ src/ripple/unity/net.cpp | 1 + 3 files changed, 337 insertions(+) create mode 100644 src/ripple/net/SSLHTTPDownloader.h create mode 100644 src/ripple/net/impl/SSLHTTPDownloader.cpp diff --git a/src/ripple/net/SSLHTTPDownloader.h b/src/ripple/net/SSLHTTPDownloader.h new file mode 100644 index 000000000..7d9ba3696 --- /dev/null +++ b/src/ripple/net/SSLHTTPDownloader.h @@ -0,0 +1,85 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2012, 2018 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef RIPPLE_NET_SSLHTTPDOWNLOADER_H_INCLUDED +#define RIPPLE_NET_SSLHTTPDOWNLOADER_H_INCLUDED + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace ripple { + +/** Provides an asynchronous HTTPS file downloader +*/ +class SSLHTTPDownloader + : public std::enable_shared_from_this +{ +public: + using error_code = boost::system::error_code; + + SSLHTTPDownloader( + boost::asio::io_service& io_service, + beast::Journal j); + + bool + init(Config const& config); + + bool + download( + std::string const& host, + std::string const& port, + std::string const& target, + int version, + boost::filesystem::path const& dstPath, + std::function complete); + +private: + boost::asio::ssl::context ctx_; + boost::asio::io_service::strand strand_; + boost::asio::ssl::stream stream_; + boost::beast::flat_buffer read_buf_; + beast::Journal j_; + + void + do_session( + std::string host, + std::string port, + std::string target, + int version, + boost::filesystem::path dstPath, + std::function complete, + boost::asio::yield_context yield); +}; + +} // ripple + +#endif diff --git a/src/ripple/net/impl/SSLHTTPDownloader.cpp b/src/ripple/net/impl/SSLHTTPDownloader.cpp new file mode 100644 index 000000000..9a5979391 --- /dev/null +++ b/src/ripple/net/impl/SSLHTTPDownloader.cpp @@ -0,0 +1,251 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2012, 2013 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include +#include + +namespace ripple { + +SSLHTTPDownloader::SSLHTTPDownloader( + boost::asio::io_service& io_service, + beast::Journal j) + : ctx_(boost::asio::ssl::context::sslv23_client) + , strand_(io_service) + , stream_(io_service, ctx_) + , j_(j) +{ +} + +bool +SSLHTTPDownloader::init(Config const& config) +{ + boost::system::error_code ec; + if (config.SSL_VERIFY_FILE.empty()) + { + registerSSLCerts(ctx_, ec, j_); + if (ec && config.SSL_VERIFY_DIR.empty()) + { + JLOG(j_.error()) << + "Failed to set_default_verify_paths: " << + ec.message(); + return false; + } + } + else + ctx_.load_verify_file(config.SSL_VERIFY_FILE); + + if (!config.SSL_VERIFY_DIR.empty()) + { + ctx_.add_verify_path(config.SSL_VERIFY_DIR, ec); + if (ec) + { + JLOG(j_.error()) << + "Failed to add verify path: " << + ec.message(); + return false; + } + } + return true; +} + +bool +SSLHTTPDownloader::download( + std::string const& host, + std::string const& port, + std::string const& target, + int version, + boost::filesystem::path const& dstPath, + std::function complete) +{ + try + { + if (exists(dstPath)) + { + JLOG(j_.error()) << + "Destination file exists"; + return false; + } + } + catch (std::exception const& e) + { + JLOG(j_.error()) << + "exception: " << e.what(); + return false; + } + + if (!strand_.running_in_this_thread()) + strand_.post( + std::bind( + &SSLHTTPDownloader::download, + this->shared_from_this(), + host, + port, + target, + version, + dstPath, + complete)); + else + boost::asio::spawn( + strand_, + std::bind( + &SSLHTTPDownloader::do_session, + this->shared_from_this(), + host, + port, + target, + version, + dstPath, + complete, + std::placeholders::_1)); + return true; +} + +void +SSLHTTPDownloader::do_session( + std::string const host, + std::string const port, + std::string const target, + int version, + boost::filesystem::path dstPath, + std::function complete, + boost::asio::yield_context yield) +{ + using namespace boost::asio; + using namespace boost::beast; + + boost::system::error_code ec; + auto fail = [&](std::string errMsg) + { + if (ec != boost::asio::error::operation_aborted) + { + JLOG(j_.error()) << + errMsg << ": " << ec.message(); + } + try + { + remove(dstPath); + } + catch (std::exception const& e) + { + JLOG(j_.error()) << + "exception: " << e.what(); + } + complete(std::move(dstPath)); + }; + + if (!SSL_set_tlsext_host_name(stream_.native_handle(), host.c_str())) + { + ec.assign(static_cast( + ::ERR_get_error()), boost::asio::error::get_ssl_category()); + return fail("SSL_set_tlsext_host_name"); + } + + ip::tcp::resolver resolver {stream_.get_io_context()}; + auto const results = resolver.async_resolve(host, port, yield[ec]); + if (ec) + return fail("async_resolve"); + + boost::asio::async_connect( + stream_.next_layer(), results.begin(), results.end(), yield[ec]); + if (ec) + return fail("async_connect"); + + stream_.async_handshake(ssl::stream_base::client, yield[ec]); + if (ec) + return fail("async_handshake"); + + // Set up an HTTP HEAD request message to find the file size + http::request req {http::verb::head, target, version}; + req.set(http::field::host, host); + req.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING); + + http::async_write(stream_, req, yield[ec]); + if(ec) + return fail("async_write"); + + { + // Check if available storage for file size + http::response_parser p; + p.skip(true); + http::async_read(stream_, read_buf_, p, yield[ec]); + if(ec) + return fail("async_read"); + if (auto len = p.content_length()) + { + try + { + if (*len > space(dstPath.parent_path()).available) + return fail("Insufficient disk space for download"); + } + catch (std::exception const& e) + { + JLOG(j_.error()) << + "exception: " << e.what(); + return fail({}); + } + } + } + + // Set up an HTTP GET request message to download the file + req.method(http::verb::get); + http::async_write(stream_, req, yield[ec]); + if(ec) + return fail("async_write"); + + // Download the file + http::response_parser p; + p.body_limit(std::numeric_limits::max()); + p.get().body().open( + dstPath.string().c_str(), + boost::beast::file_mode::write, + ec); + if (ec) + { + p.get().body().close(); + return fail("open"); + } + + http::async_read(stream_, read_buf_, p, yield[ec]); + if (ec) + { + p.get().body().close(); + return fail("async_read"); + } + p.get().body().close(); + + // Gracefully close the stream + stream_.async_shutdown(yield[ec]); + if (ec == boost::asio::error::eof) + ec.assign(0, ec.category()); + if (ec) + { + // Most web servers don't bother with performing + // the SSL shutdown handshake, for speed. + JLOG(j_.trace()) << + "async_shutdown: " << ec.message(); + } + + JLOG(j_.trace()) << + "download completed: " << dstPath.string(); + + // Notify the completion handler + complete(std::move(dstPath)); +} + +}// ripple diff --git a/src/ripple/unity/net.cpp b/src/ripple/unity/net.cpp index e9a1d4366..1527f6708 100644 --- a/src/ripple/unity/net.cpp +++ b/src/ripple/unity/net.cpp @@ -26,3 +26,4 @@ #include #include #include +#include