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