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  bool isPaused)
30  : j_(j)
31  , ssl_ctx_(config, j, boost::asio::ssl::context::tlsv12_client)
32  , strand_(io_service)
33  , isStopped_(false)
34  , sessionActive_(false)
35 {
36 }
37 
38 bool
40  std::string const& host,
41  std::string const& port,
42  std::string const& target,
43  int version,
44  boost::filesystem::path const& dstPath,
45  std::function<void(boost::filesystem::path)> complete)
46 {
47  if (!checkPath(dstPath))
48  return false;
49 
50  if (!strand_.running_in_this_thread())
51  strand_.post(std::bind(
53  this->shared_from_this(),
54  host,
55  port,
56  target,
57  version,
58  dstPath,
59  complete));
60  else
61  boost::asio::spawn(
62  strand_,
63  std::bind(
65  this->shared_from_this(),
66  host,
67  port,
68  target,
69  version,
70  dstPath,
71  complete,
72  std::placeholders::_1));
73  return true;
74 }
75 
76 void
78 {
79  std::unique_lock lock(m_);
80 
81  isStopped_ = true;
82 
83  if (sessionActive_)
84  {
85  // Wait for the handler to exit.
86  c_.wait(lock, [this]() { return !sessionActive_; });
87  }
88 }
89 
90 void
92  std::string const host,
93  std::string const port,
94  std::string const target,
95  int version,
96  boost::filesystem::path dstPath,
97  std::function<void(boost::filesystem::path)> complete,
98  boost::asio::yield_context yield)
99 {
100  using namespace boost::asio;
101  using namespace boost::beast;
102 
103  boost::system::error_code ec;
104  bool skip = false;
105 
107  // Define lambdas for encapsulating download
108  // operations:
109  auto connect = [&](std::shared_ptr<parser> parser) {
110  uint64_t const rangeStart = size(parser);
111 
112  ip::tcp::resolver resolver{strand_.context()};
113  auto const results = resolver.async_resolve(host, port, yield[ec]);
114  if (ec)
115  return fail(dstPath, complete, ec, "async_resolve", parser);
116 
117  try
118  {
119  stream_.emplace(strand_.context(), ssl_ctx_.context());
120  }
121  catch (std::exception const& e)
122  {
123  return fail(
124  dstPath,
125  complete,
126  ec,
127  std::string("exception: ") + e.what(),
128  parser);
129  }
130 
131  ec = ssl_ctx_.preConnectVerify(*stream_, host);
132  if (ec)
133  return fail(dstPath, complete, ec, "preConnectVerify", parser);
134 
135  boost::asio::async_connect(
136  stream_->next_layer(), results.begin(), results.end(), yield[ec]);
137  if (ec)
138  return fail(dstPath, complete, ec, "async_connect", parser);
139 
140  ec = ssl_ctx_.postConnectVerify(*stream_, host);
141  if (ec)
142  return fail(dstPath, complete, ec, "postConnectVerify", parser);
143 
144  stream_->async_handshake(ssl::stream_base::client, yield[ec]);
145  if (ec)
146  return fail(dstPath, complete, ec, "async_handshake", parser);
147 
148  // Set up an HTTP HEAD request message to find the file size
149  http::request<http::empty_body> req{http::verb::head, target, version};
150  req.set(http::field::host, host);
151  req.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING);
152 
153  // Requesting a portion of the file
154  if (rangeStart)
155  {
156  req.set(
157  http::field::range,
158  (boost::format("bytes=%llu-") % rangeStart).str());
159  }
160 
161  http::async_write(*stream_, req, yield[ec]);
162  if (ec)
163  return fail(dstPath, complete, ec, "async_write", parser);
164 
165  {
166  // Check if available storage for file size
167  http::response_parser<http::empty_body> p;
168  p.skip(true);
169  http::async_read(*stream_, read_buf_, p, yield[ec]);
170  if (ec)
171  return fail(dstPath, complete, ec, "async_read", parser);
172 
173  // Range request was rejected
174  if (p.get().result() == http::status::range_not_satisfiable)
175  {
176  req.erase(http::field::range);
177 
178  http::async_write(*stream_, req, yield[ec]);
179  if (ec)
180  return fail(
181  dstPath,
182  complete,
183  ec,
184  "async_write_range_verify",
185  parser);
186 
187  http::response_parser<http::empty_body> p;
188  p.skip(true);
189 
190  http::async_read(*stream_, read_buf_, p, yield[ec]);
191  if (ec)
192  return fail(
193  dstPath,
194  complete,
195  ec,
196  "async_read_range_verify",
197  parser);
198 
199  // The entire file is downloaded already.
200  if (p.content_length() == rangeStart)
201  skip = true;
202  else
203  return fail(
204  dstPath, complete, ec, "range_not_satisfiable", parser);
205  }
206  else if (
207  rangeStart && p.get().result() != http::status::partial_content)
208  {
209  ec.assign(
210  boost::system::errc::not_supported,
211  boost::system::generic_category());
212 
213  return fail(
214  dstPath, complete, ec, "Range request ignored", parser);
215  }
216  else if (auto len = p.content_length())
217  {
218  try
219  {
220  if (*len > space(dstPath.parent_path()).available)
221  {
222  return fail(
223  dstPath,
224  complete,
225  ec,
226  "Insufficient disk space for download",
227  parser);
228  }
229  }
230  catch (std::exception const& e)
231  {
232  return fail(
233  dstPath,
234  complete,
235  ec,
236  std::string("exception: ") + e.what(),
237  parser);
238  }
239  }
240  }
241 
242  if (!skip)
243  {
244  // Set up an HTTP GET request message to download the file
245  req.method(http::verb::get);
246 
247  if (rangeStart)
248  {
249  req.set(
250  http::field::range,
251  (boost::format("bytes=%llu-") % rangeStart).str());
252  }
253  }
254 
255  http::async_write(*stream_, req, yield[ec]);
256  if (ec)
257  return fail(dstPath, complete, ec, "async_write", parser);
258 
259  return true;
260  };
261 
262  auto close = [&](auto p) {
263  closeBody(p);
264 
265  // Gracefully close the stream
266  stream_->async_shutdown(yield[ec]);
267  if (ec == boost::asio::error::eof)
268  ec.assign(0, ec.category());
269  if (ec)
270  {
271  // Most web servers don't bother with performing
272  // the SSL shutdown handshake, for speed.
273  JLOG(j_.trace()) << "async_shutdown: " << ec.message();
274  }
275  // The socket cannot be reused
276  stream_ = boost::none;
277  };
278 
279  auto getParser = [&] {
280  auto p = this->getParser(dstPath, complete, ec);
281  if (ec)
282  fail(dstPath, complete, ec, "getParser", p);
283 
284  return p;
285  };
286 
287  // When the downloader is being stopped
288  // because the server is shutting down,
289  // this method notifies a 'Stoppable'
290  // object that the session has ended.
291  auto exit = [this]() {
293  sessionActive_ = false;
294  c_.notify_one();
295  };
296 
297  // end lambdas
299 
300  {
302  sessionActive_ = true;
303  }
304 
305  if (isStopped_.load())
306  return exit();
307 
308  auto p = getParser();
309  if (ec)
310  return exit();
311 
312  if (!connect(p) || ec)
313  return exit();
314 
315  if (skip)
316  p->skip(true);
317 
318  // Download the file
319  while (!p->is_done())
320  {
321  if (isStopped_.load())
322  {
323  close(p);
324  return exit();
325  }
326 
327  http::async_read_some(*stream_, read_buf_, *p, yield[ec]);
328  }
329 
330  JLOG(j_.trace()) << "download completed: " << dstPath.string();
331 
332  close(p);
333  exit();
334 
335  // Notify the completion handler
336  complete(std::move(dstPath));
337 }
338 
339 bool
341  boost::filesystem::path dstPath,
342  std::function<void(boost::filesystem::path)> const& complete,
343  boost::system::error_code const& ec,
344  std::string const& errMsg,
346 {
347  if (!ec)
348  {
349  JLOG(j_.error()) << errMsg;
350  }
351  else if (ec != boost::asio::error::operation_aborted)
352  {
353  JLOG(j_.error()) << errMsg << ": " << ec.message();
354  }
355 
356  if (parser)
357  closeBody(parser);
358 
359  try
360  {
361  remove(dstPath);
362  }
363  catch (std::exception const& e)
364  {
365  JLOG(j_.error()) << "exception: " << e.what()
366  << " in function: " << __func__;
367  }
368  complete(std::move(dstPath));
369 
370  return false;
371 }
372 
373 } // namespace ripple
ripple::SSLHTTPDownloader::parser
boost::beast::http::basic_parser< false > parser
Definition: SSLHTTPDownloader.h:70
ripple::SSLHTTPDownloader::j_
const beast::Journal j_
Definition: SSLHTTPDownloader.h:72
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:77
ripple::HTTPClientSSLContext::context
boost::asio::ssl::context & context()
Definition: HTTPClientSSLContext.h:72
ripple::SSLHTTPDownloader::strand_
boost::asio::io_service::strand strand_
Definition: SSLHTTPDownloader.h:84
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:91
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.
ripple::SSLHTTPDownloader::fail
bool 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=nullptr)
Definition: SSLHTTPDownloader.cpp:340
boost
Definition: IPAddress.h:117
std::function
ripple::SSLHTTPDownloader::closeBody
virtual void closeBody(std::shared_ptr< parser > p)=0
ripple::SSLHTTPDownloader::read_buf_
boost::beast::flat_buffer read_buf_
Definition: SSLHTTPDownloader.h:87
ripple::SSLHTTPDownloader::isStopped_
std::atomic< bool > isStopped_
Definition: SSLHTTPDownloader.h:88
std::enable_shared_from_this< SSLHTTPDownloader >::shared_from_this
T shared_from_this(T... args)
std::atomic::load
T load(T... args)
ripple::Config
Definition: Config.h:66
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::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
ripple::SSLHTTPDownloader::SSLHTTPDownloader
SSLHTTPDownloader(boost::asio::io_service &io_service, beast::Journal j, Config const &config, bool isPaused=false)
Definition: SSLHTTPDownloader.cpp:25
std::condition_variable::notify_one
T notify_one(T... args)
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:39
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:86
ripple::SSLHTTPDownloader::c_
std::condition_variable c_
Definition: SSLHTTPDownloader.h:91
ripple::SSLHTTPDownloader::ssl_ctx_
HTTPClientSSLContext ssl_ctx_
Definition: SSLHTTPDownloader.h:83
ripple::SSLHTTPDownloader::sessionActive_
bool sessionActive_
Definition: SSLHTTPDownloader.h:89
std::exception::what
T what(T... args)
ripple::SSLHTTPDownloader::m_
std::mutex m_
Definition: SSLHTTPDownloader.h:90