rippled
SSLHTTPDownloader.cpp
1 //------------------------------------------------------------------------------
2 /*
3  This file is part of rippled: https://github.com/ripple/rippled
4  Copyright (c) 2012, 2013 Ripple Labs Inc.
5 
6  Permission to use, copy, modify, and/or distribute this software for any
7  purpose with or without fee is hereby granted, provided that the above
8  copyright notice and this permission notice appear in all copies.
9 
10  THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18 //==============================================================================
19 
20 #include <ripple/net/SSLHTTPDownloader.h>
21 #include <boost/asio/ssl.hpp>
22 
23 namespace ripple {
24 
26  boost::asio::io_service& io_service,
28  Config const& config)
29  : j_(j)
30  , ssl_ctx_(config, j, boost::asio::ssl::context::tlsv12_client)
31  , strand_(io_service)
32  , cancelDownloads_(false)
33  , sessionActive_(false)
34 {
35 }
36 
37 bool
39  std::string const& host,
40  std::string const& port,
41  std::string const& target,
42  int version,
43  boost::filesystem::path const& dstPath,
44  std::function<void(boost::filesystem::path)> complete)
45 {
46  if (!checkPath(dstPath))
47  return false;
48 
49  {
50  std::lock_guard lock(m_);
51 
52  if (cancelDownloads_)
53  return true;
54 
55  sessionActive_ = true;
56  }
57 
58  if (!strand_.running_in_this_thread())
59  strand_.post(std::bind(
61  this,
62  host,
63  port,
64  target,
65  version,
66  dstPath,
67  complete));
68  else
69  boost::asio::spawn(
70  strand_,
71  std::bind(
73  this,
74  host,
75  port,
76  target,
77  version,
78  dstPath,
79  complete,
80  std::placeholders::_1));
81  return true;
82 }
83 
84 void
86 {
87  std::unique_lock lock(m_);
88 
89  cancelDownloads_ = true;
90 
91  if (sessionActive_)
92  {
93  // Wait for the handler to exit.
94  c_.wait(lock, [this]() { return !sessionActive_; });
95  }
96 }
97 
98 void
100  std::string const host,
101  std::string const port,
102  std::string const target,
103  int version,
104  boost::filesystem::path dstPath,
105  std::function<void(boost::filesystem::path)> complete,
106  boost::asio::yield_context yield)
107 {
108  using namespace boost::asio;
109  using namespace boost::beast;
110 
111  boost::system::error_code ec;
112  bool skip = false;
113 
115  // Define lambdas for encapsulating download
116  // operations:
117  auto close = [&](auto p) {
118  closeBody(p);
119 
120  // Gracefully close the stream
121  stream_->async_shutdown(yield[ec]);
122  if (ec == boost::asio::error::eof)
123  ec.assign(0, ec.category());
124  if (ec)
125  {
126  // Most web servers don't bother with performing
127  // the SSL shutdown handshake, for speed.
128  JLOG(j_.trace()) << "async_shutdown: " << ec.message();
129  }
130  // The socket cannot be reused
131  stream_ = boost::none;
132  };
133 
134  // When the downloader is being stopped
135  // because the server is shutting down,
136  // this method notifies a 'Stoppable'
137  // object that the session has ended.
138  auto exit = [this]() {
140  sessionActive_ = false;
141  c_.notify_one();
142  };
143 
144  auto failAndExit = [&exit, &dstPath, complete, &ec, this](
145  std::string const& errMsg, auto p) {
146  exit();
147  fail(dstPath, complete, ec, errMsg, p);
148  };
149  // end lambdas
151 
152  if (cancelDownloads_.load())
153  return exit();
154 
155  auto p = this->getParser(dstPath, complete, ec);
156  if (ec)
157  return failAndExit("getParser", p);
158 
160  // Prepare for download and establish the
161  // connection:
162  std::uint64_t const rangeStart = size(p);
163 
164  ip::tcp::resolver resolver{strand_.context()};
165  auto const results = resolver.async_resolve(host, port, yield[ec]);
166  if (ec)
167  return failAndExit("async_resolve", p);
168 
169  try
170  {
171  stream_.emplace(strand_.context(), ssl_ctx_.context());
172  }
173  catch (std::exception const& e)
174  {
175  return failAndExit(std::string("exception: ") + e.what(), p);
176  }
177 
178  ec = ssl_ctx_.preConnectVerify(*stream_, host);
179  if (ec)
180  return failAndExit("preConnectVerify", p);
181 
182  boost::asio::async_connect(
183  stream_->next_layer(), results.begin(), results.end(), yield[ec]);
184  if (ec)
185  return failAndExit("async_connect", p);
186 
187  ec = ssl_ctx_.postConnectVerify(*stream_, host);
188  if (ec)
189  return failAndExit("postConnectVerify", p);
190 
191  stream_->async_handshake(ssl::stream_base::client, yield[ec]);
192  if (ec)
193  return failAndExit("async_handshake", p);
194 
195  // Set up an HTTP HEAD request message to find the file size
196  http::request<http::empty_body> req{http::verb::head, target, version};
197  req.set(http::field::host, host);
198  req.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING);
199 
200  // Requesting a portion of the file
201  if (rangeStart)
202  {
203  req.set(
204  http::field::range,
205  (boost::format("bytes=%llu-") % rangeStart).str());
206  }
207 
208  http::async_write(*stream_, req, yield[ec]);
209  if (ec)
210  return failAndExit("async_write", p);
211 
212  {
213  // Read the response
214  http::response_parser<http::empty_body> connectParser;
215  connectParser.skip(true);
216  http::async_read(*stream_, read_buf_, connectParser, yield[ec]);
217  if (ec)
218  return failAndExit("async_read", p);
219 
220  // Range request was rejected
221  if (connectParser.get().result() == http::status::range_not_satisfiable)
222  {
223  req.erase(http::field::range);
224 
225  http::async_write(*stream_, req, yield[ec]);
226  if (ec)
227  return failAndExit("async_write_range_verify", p);
228 
229  http::response_parser<http::empty_body> rangeParser;
230  rangeParser.skip(true);
231 
232  http::async_read(*stream_, read_buf_, rangeParser, yield[ec]);
233  if (ec)
234  return failAndExit("async_read_range_verify", p);
235 
236  // The entire file is downloaded already.
237  if (rangeParser.content_length() == rangeStart)
238  skip = true;
239  else
240  return failAndExit("range_not_satisfiable", p);
241  }
242  else if (
243  rangeStart &&
244  connectParser.get().result() != http::status::partial_content)
245  {
246  ec.assign(
247  boost::system::errc::not_supported,
248  boost::system::generic_category());
249 
250  return failAndExit("Range request ignored", p);
251  }
252  else if (auto len = connectParser.content_length())
253  {
254  try
255  {
256  // Ensure sufficient space is available
257  if (*len > space(dstPath.parent_path()).available)
258  {
259  return failAndExit(
260  "Insufficient disk space for download", p);
261  }
262  }
263  catch (std::exception const& e)
264  {
265  return failAndExit(std::string("exception: ") + e.what(), p);
266  }
267  }
268  }
269 
270  if (!skip)
271  {
272  // Set up an HTTP GET request message to download the file
273  req.method(http::verb::get);
274 
275  if (rangeStart)
276  {
277  req.set(
278  http::field::range,
279  (boost::format("bytes=%llu-") % rangeStart).str());
280  }
281  }
282 
283  http::async_write(*stream_, req, yield[ec]);
284  if (ec)
285  return failAndExit("async_write", p);
286 
287  // end prepare and connect
289 
290  if (skip)
291  p->skip(true);
292 
293  // Download the file
294  while (!p->is_done())
295  {
296  if (cancelDownloads_.load())
297  {
298  close(p);
299  return exit();
300  }
301 
302  http::async_read_some(*stream_, read_buf_, *p, yield[ec]);
303  }
304 
305  JLOG(j_.trace()) << "download completed: " << dstPath.string();
306 
307  close(p);
308  exit();
309 
310  // Notify the completion handler
311  complete(std::move(dstPath));
312 }
313 
314 void
316  boost::filesystem::path dstPath,
317  std::function<void(boost::filesystem::path)> const& complete,
318  boost::system::error_code const& ec,
319  std::string const& errMsg,
321 {
322  if (!ec)
323  {
324  JLOG(j_.error()) << errMsg;
325  }
326  else if (ec != boost::asio::error::operation_aborted)
327  {
328  JLOG(j_.error()) << errMsg << ": " << ec.message();
329  }
330 
331  if (parser)
332  closeBody(parser);
333 
334  try
335  {
336  remove(dstPath);
337  }
338  catch (std::exception const& e)
339  {
340  JLOG(j_.error()) << "exception: " << e.what()
341  << " in function: " << __func__;
342  }
343  complete(std::move(dstPath));
344 }
345 
346 } // namespace ripple
ripple::SSLHTTPDownloader::parser
boost::beast::http::basic_parser< false > parser
Definition: SSLHTTPDownloader.h:69
ripple::SSLHTTPDownloader::j_
const beast::Journal j_
Definition: SSLHTTPDownloader.h:71
std::bind
T bind(T... args)
ripple::SSLHTTPDownloader::size
virtual uint64_t size(std::shared_ptr< parser > p)=0
std::string
STL class.
std::shared_ptr
STL class.
std::exception
STL class.
beast::Journal::trace
Stream trace() const
Severity stream access functions.
Definition: Journal.h:309
ripple::SSLHTTPDownloader::onStop
void onStop()
Definition: SSLHTTPDownloader.cpp:85
ripple::HTTPClientSSLContext::context
boost::asio::ssl::context & context()
Definition: HTTPClientSSLContext.h:72
ripple::SSLHTTPDownloader::strand_
boost::asio::io_service::strand strand_
Definition: SSLHTTPDownloader.h:83
ripple::SSLHTTPDownloader::checkPath
virtual bool checkPath(boost::filesystem::path const &dstPath)=0
ripple::SSLHTTPDownloader::do_session
void do_session(std::string host, std::string port, std::string target, int version, boost::filesystem::path dstPath, std::function< void(boost::filesystem::path)> complete, boost::asio::yield_context yield)
Definition: SSLHTTPDownloader.cpp:99
ripple::HTTPClientSSLContext::preConnectVerify
boost::system::error_code preConnectVerify(T &strm, std::string const &host)
invoked before connect/async_connect on an ssl stream to setup name verification.
Definition: HTTPClientSSLContext.h:107
std::lock_guard
STL class.
boost
Definition: IPAddress.h:117
std::function
ripple::SSLHTTPDownloader::closeBody
virtual void closeBody(std::shared_ptr< parser > p)=0
ripple::SSLHTTPDownloader::SSLHTTPDownloader
SSLHTTPDownloader(boost::asio::io_service &io_service, beast::Journal j, Config const &config)
Definition: SSLHTTPDownloader.cpp:25
ripple::SSLHTTPDownloader::read_buf_
boost::beast::flat_buffer read_buf_
Definition: SSLHTTPDownloader.h:86
std::atomic::load
T load(T... args)
ripple::Config
Definition: Config.h:67
std::unique_lock
STL class.
boost::asio
Definition: Overlay.h:42
ripple::HTTPClientSSLContext::postConnectVerify
boost::system::error_code postConnectVerify(T &strm, std::string const &host)
invoked after connect/async_connect but before sending data on an ssl stream - to setup name verifica...
Definition: HTTPClientSSLContext.h:142
beast::Journal::error
Stream error() const
Definition: Journal.h:333
beast::Journal
A generic endpoint for log messages.
Definition: Journal.h:58
std::uint64_t
std::condition_variable::wait
T wait(T... args)
ripple::SSLHTTPDownloader::getParser
virtual std::shared_ptr< parser > getParser(boost::filesystem::path dstPath, std::function< void(boost::filesystem::path)> complete, boost::system::error_code &ec)=0
std::condition_variable::notify_one
T notify_one(T... args)
ripple::SSLHTTPDownloader::fail
void fail(boost::filesystem::path dstPath, std::function< void(boost::filesystem::path)> const &complete, boost::system::error_code const &ec, std::string const &errMsg, std::shared_ptr< parser > parser)
Definition: SSLHTTPDownloader.cpp:315
ripple::SSLHTTPDownloader::download
bool download(std::string const &host, std::string const &port, std::string const &target, int version, boost::filesystem::path const &dstPath, std::function< void(boost::filesystem::path)> complete)
Definition: SSLHTTPDownloader.cpp:38
ripple
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: RCLCensorshipDetector.h:29
ripple::SSLHTTPDownloader::stream_
boost::optional< boost::asio::ssl::stream< boost::asio::ip::tcp::socket > > stream_
Definition: SSLHTTPDownloader.h:85
ripple::SSLHTTPDownloader::c_
std::condition_variable c_
Definition: SSLHTTPDownloader.h:92
ripple::SSLHTTPDownloader::ssl_ctx_
HTTPClientSSLContext ssl_ctx_
Definition: SSLHTTPDownloader.h:82
ripple::SSLHTTPDownloader::sessionActive_
bool sessionActive_
Definition: SSLHTTPDownloader.h:91
ripple::SSLHTTPDownloader::cancelDownloads_
std::atomic< bool > cancelDownloads_
Definition: SSLHTTPDownloader.h:87
std::exception::what
T what(T... args)
ripple::SSLHTTPDownloader::m_
std::mutex m_
Definition: SSLHTTPDownloader.h:90