https_download.hpp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487
  1. /*
  2. * Copyright (c) 2017-2023 zhllxt
  3. *
  4. * author : zhllxt
  5. * email : 37792738@qq.com
  6. *
  7. * Distributed under the Boost Software License, Version 1.0. (See accompanying
  8. * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
  9. */
  10. #if defined(ASIO2_ENABLE_SSL) || defined(ASIO2_USE_SSL)
  11. #ifndef __ASIO2_HTTPS_DOWNLOAD_HPP__
  12. #define __ASIO2_HTTPS_DOWNLOAD_HPP__
  13. #if defined(_MSC_VER) && (_MSC_VER >= 1200)
  14. #pragma once
  15. #endif // defined(_MSC_VER) && (_MSC_VER >= 1200)
  16. #include <asio2/base/detail/push_options.hpp>
  17. #include <fstream>
  18. #include <asio2/base/detail/function_traits.hpp>
  19. #include <asio2/util/string.hpp>
  20. #include <asio2/http/detail/http_util.hpp>
  21. #include <asio2/http/detail/http_make.hpp>
  22. #include <asio2/http/detail/http_traits.hpp>
  23. #include <asio2/component/socks/socks5_client_cp.hpp>
  24. namespace asio2::detail
  25. {
  26. template<class derived_t, class args_t, bool Enable = is_https_execute_download_enabled<args_t>::value()>
  27. struct https_download_impl_bridge;
  28. template<class derived_t, class args_t>
  29. struct https_download_impl_bridge<derived_t, args_t, false>
  30. {
  31. };
  32. template<class derived_t, class args_t>
  33. struct https_download_impl_bridge<derived_t, args_t, true>
  34. {
  35. protected:
  36. template<typename String, typename StrOrInt, class HeaderCallback, class BodyCallback, class Proxy,
  37. class Body, class Fields, class Buffer>
  38. static void _download_with_socks5(
  39. asio::io_context& ioc, asio::ip::tcp::resolver& resolver, asio::ip::tcp::socket& socket,
  40. asio::ssl::stream<asio::ip::tcp::socket&>& stream,
  41. Buffer& buffer,
  42. String&& host, StrOrInt&& port,
  43. http::request<Body, Fields>& req, HeaderCallback&& cbh, BodyCallback&& cbb, Proxy&& proxy)
  44. {
  45. auto sk5 = detail::to_shared_ptr(std::forward<Proxy>(proxy));
  46. std::string_view h{ sk5->host() };
  47. std::string_view p{ sk5->port() };
  48. if (static_cast<int>(sk5->command()) == 0)
  49. {
  50. sk5->command(socks5::command::connect);
  51. }
  52. // Look up the domain name
  53. resolver.async_resolve(h, p, [&, s5 = std::move(sk5)]
  54. (const error_code& ec1, const asio::ip::tcp::resolver::results_type& endpoints) mutable
  55. {
  56. if (ec1) { set_last_error(ec1); return; }
  57. // Make the connection on the IP address we get from a lookup
  58. asio::async_connect(socket, endpoints,
  59. [&, s5 = std::move(s5)](const error_code& ec2, const asio::ip::tcp::endpoint&) mutable
  60. {
  61. if (ec2) { set_last_error(ec2); return; }
  62. socks5_async_handshake
  63. (
  64. detail::to_string(std::forward<String >(host)),
  65. detail::to_string(std::forward<StrOrInt>(port)),
  66. socket,
  67. std::move(s5),
  68. [&](error_code ecs5, std::string, std::string) mutable
  69. {
  70. if (ecs5) { set_last_error(ecs5); return; }
  71. // https://github.com/djarek/certify
  72. if (auto it = req.find(http::field::host); it != req.end())
  73. {
  74. std::string hostname(it->value());
  75. SSL_set_tlsext_host_name(stream.native_handle(), hostname.data());
  76. }
  77. stream.async_handshake(asio::ssl::stream_base::client,
  78. [&](const error_code& ec3) mutable
  79. {
  80. if (ec3) { set_last_error(ec3); return; }
  81. http::async_write(stream, req, [&](const error_code& ec4, std::size_t) mutable
  82. {
  83. // can't use stream.shutdown(),in some case the shutdowm will blocking forever.
  84. if (ec4) { set_last_error(ec4); stream.async_shutdown([](const error_code&) {}); return; }
  85. http::read_large_body<false>(stream, buffer,
  86. std::forward<HeaderCallback>(cbh), std::forward<BodyCallback>(cbb));
  87. stream.async_shutdown([](const error_code&) mutable {});
  88. });
  89. });
  90. }
  91. );
  92. });
  93. });
  94. }
  95. template<typename String, typename StrOrInt, class HeaderCallback, class BodyCallback,
  96. class Body, class Fields, class Buffer>
  97. static void _download_trivially(
  98. asio::io_context& ioc, asio::ip::tcp::resolver& resolver, asio::ip::tcp::socket& socket,
  99. asio::ssl::stream<asio::ip::tcp::socket&>& stream,
  100. Buffer& buffer,
  101. String&& host, StrOrInt&& port,
  102. http::request<Body, Fields>& req, HeaderCallback&& cbh, BodyCallback&& cbb)
  103. {
  104. detail::ignore_unused(ioc);
  105. // Look up the domain name
  106. resolver.async_resolve(std::forward<String>(host), detail::to_string(std::forward<StrOrInt>(port)),
  107. [&](const error_code& ec1, const asio::ip::tcp::resolver::results_type& endpoints) mutable
  108. {
  109. if (ec1) { set_last_error(ec1); return; }
  110. // Make the connection on the IP address we get from a lookup
  111. asio::async_connect(socket, endpoints,
  112. [&](const error_code& ec2, const asio::ip::tcp::endpoint&) mutable
  113. {
  114. if (ec2) { set_last_error(ec2); return; }
  115. // https://github.com/djarek/certify
  116. if (auto it = req.find(http::field::host); it != req.end())
  117. {
  118. std::string hostname(it->value());
  119. SSL_set_tlsext_host_name(stream.native_handle(), hostname.data());
  120. }
  121. stream.async_handshake(asio::ssl::stream_base::client,
  122. [&](const error_code& ec3) mutable
  123. {
  124. if (ec3) { set_last_error(ec3); return; }
  125. http::async_write(stream, req, [&](const error_code& ec4, std::size_t) mutable
  126. {
  127. // can't use stream.shutdown(),in some case the shutdowm will blocking forever.
  128. if (ec4) { set_last_error(ec4); stream.async_shutdown([](const error_code&) {}); return; }
  129. http::read_large_body<false>(stream, buffer,
  130. std::forward<HeaderCallback>(cbh), std::forward<BodyCallback>(cbb));
  131. stream.async_shutdown([](const error_code&) mutable {});
  132. });
  133. });
  134. });
  135. });
  136. }
  137. template<typename String, typename StrOrInt, class HeaderCallback, class BodyCallback, class Proxy,
  138. class Body, class Fields, class Buffer>
  139. static void _download_impl(
  140. asio::io_context& ioc, asio::ip::tcp::resolver& resolver, asio::ip::tcp::socket& socket,
  141. asio::ssl::stream<asio::ip::tcp::socket&>& stream,
  142. Buffer& buffer,
  143. String&& host, StrOrInt&& port,
  144. http::request<Body, Fields>& req, HeaderCallback&& cbh, BodyCallback&& cbb, Proxy&& proxy)
  145. {
  146. // if has socks5 proxy
  147. if constexpr (std::is_base_of_v<asio2::socks5::option_base,
  148. typename detail::element_type_adapter<detail::remove_cvref_t<Proxy>>::type>)
  149. {
  150. derived_t::_download_with_socks5(ioc, resolver, socket, stream, buffer
  151. , std::forward<String>(host), std::forward<StrOrInt>(port)
  152. , req, std::forward<HeaderCallback>(cbh), std::forward<BodyCallback>(cbb)
  153. , std::forward<Proxy>(proxy)
  154. );
  155. }
  156. else
  157. {
  158. detail::ignore_unused(proxy);
  159. derived_t::_download_trivially(ioc, resolver, socket, stream, buffer
  160. , std::forward<String>(host), std::forward<StrOrInt>(port)
  161. , req, std::forward<HeaderCallback>(cbh), std::forward<BodyCallback>(cbb)
  162. );
  163. }
  164. }
  165. public:
  166. /**
  167. * @brief blocking download the http file until it is returned on success or failure
  168. * @param ctx - asio ssl context.
  169. * @param host - The ip of the server.
  170. * @param port - The port of the server.
  171. * @param req - The http request object to send to the https server.
  172. * @param cbh - A function that recv the http response header message. void(auto& message)
  173. * @param cbb - A function that circularly receives the contents of the file in chunks. void(std::string_view data)
  174. * @param proxy - socks5 proxy, if you do not need a proxy, pass "nullptr" is ok.
  175. */
  176. template<typename String, typename StrOrInt, class HeaderCallback, class BodyCallback, class Proxy,
  177. class Body = http::string_body, class Fields = http::fields, class Buffer = beast::flat_buffer>
  178. typename std::enable_if_t<detail::is_character_string_v<detail::remove_cvref_t<String>>
  179. && detail::http_proxy_checker_v<Proxy>, bool>
  180. static inline download(const asio::ssl::context& ctx, String&& host, StrOrInt&& port,
  181. http::request<Body, Fields>& req, HeaderCallback&& cbh, BodyCallback&& cbb, Proxy&& proxy)
  182. {
  183. // First assign default value 0 to last error
  184. clear_last_error();
  185. // The io_context is required for all I/O
  186. asio::io_context ioc;
  187. // These objects perform our I/O
  188. asio::ip::tcp::resolver resolver{ ioc };
  189. asio::ip::tcp::socket socket{ ioc };
  190. asio::ssl::stream<asio::ip::tcp::socket&> stream(socket, const_cast<asio::ssl::context&>(ctx));
  191. // This buffer is used for reading and must be persisted
  192. Buffer buffer;
  193. // Some sites must set the http::field::host
  194. if (req.find(http::field::host) == req.end())
  195. {
  196. std::string strhost = asio2::to_string(host);
  197. std::string strport = asio2::to_string(port);
  198. if (strport != "443")
  199. {
  200. strhost += ":";
  201. strhost += strport;
  202. }
  203. req.set(http::field::host, strhost);
  204. }
  205. // Some sites must set the http::field::user_agent
  206. if (req.find(http::field::user_agent) == req.end())
  207. {
  208. req.set(http::field::user_agent,
  209. "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36");
  210. }
  211. // do work
  212. derived_t::_download_impl(ioc, resolver, socket, stream, buffer
  213. , std::forward<String>(host), std::forward<StrOrInt>(port)
  214. , req, std::forward<HeaderCallback>(cbh), std::forward<BodyCallback>(cbb)
  215. , std::forward<Proxy>(proxy)
  216. );
  217. // timedout run
  218. ioc.run();
  219. error_code ec_ignore{};
  220. // Gracefully close the socket
  221. socket.shutdown(asio::ip::tcp::socket::shutdown_both, ec_ignore);
  222. socket.cancel(ec_ignore);
  223. socket.close(ec_ignore);
  224. return bool(!get_last_error());
  225. }
  226. // ----------------------------------------------------------------------------------------
  227. /**
  228. * @brief blocking download the http file until it is returned on success or failure
  229. * @param req - The web_request which can be create by http::make_request(...)
  230. * @param filepath - The file path to saved the received file content.
  231. */
  232. template<class String2>
  233. typename std::enable_if_t<detail::can_convert_to_string_v<detail::remove_cvref_t<String2>>, bool>
  234. static inline download(const asio::ssl::context& ctx, http::web_request& req, String2&& filepath)
  235. {
  236. std::filesystem::path path(std::forward<String2>(filepath));
  237. std::filesystem::create_directories(path.parent_path(), get_last_error());
  238. std::fstream file(path, std::ios::out | std::ios::binary | std::ios::trunc);
  239. if (!file)
  240. {
  241. set_last_error(asio::error::access_denied); // Permission denied
  242. return false;
  243. }
  244. auto cbh = [](const auto&) {};
  245. auto cbb = [&file](std::string_view chunk) mutable
  246. {
  247. file.write(chunk.data(), chunk.size());
  248. return true;
  249. };
  250. return derived_t::download(ctx, req.host(), req.port(), req.base(), cbh, cbb, std::in_place);
  251. }
  252. /**
  253. * @brief blocking download the http file until it is returned on success or failure
  254. * @param req - The web_request which can be create by http::make_request(...)
  255. * @param filepath - The file path to saved the received file content.
  256. */
  257. template<class String2>
  258. typename std::enable_if_t<detail::can_convert_to_string_v<detail::remove_cvref_t<String2>>, bool>
  259. static inline download(http::web_request& req, String2&& filepath)
  260. {
  261. return derived_t::download(asio::ssl::context{ ASIO2_DEFAULT_SSL_METHOD },
  262. req, std::forward<String2>(filepath));
  263. }
  264. /**
  265. * @brief blocking download the http file until it is returned on success or failure
  266. * @param url - The url of the file to download.
  267. * @param filepath - The file path to saved the received file content.
  268. */
  269. template<class String1, class String2>
  270. typename std::enable_if_t<
  271. detail::can_convert_to_string_v<detail::remove_cvref_t<String1>> &&
  272. detail::can_convert_to_string_v<detail::remove_cvref_t<String2>>, bool>
  273. static inline download(const asio::ssl::context& ctx, String1&& url, String2&& filepath)
  274. {
  275. http::web_request req = http::make_request(std::forward<String1>(url));
  276. if (get_last_error())
  277. return false;
  278. return derived_t::download(ctx, req, std::forward<String2>(filepath));
  279. }
  280. // ----------------------------------------------------------------------------------------
  281. /**
  282. * @brief blocking download the http file until it is returned on success or failure
  283. * @param url - The url of the file to download.
  284. * @param filepath - The file path to saved the received file content.
  285. */
  286. template<class String1, class String2>
  287. typename std::enable_if_t<
  288. detail::can_convert_to_string_v<detail::remove_cvref_t<String1>> &&
  289. detail::can_convert_to_string_v<detail::remove_cvref_t<String2>>, bool>
  290. static inline download(String1&& url, String2&& filepath)
  291. {
  292. return derived_t::download(asio::ssl::context{ ASIO2_DEFAULT_SSL_METHOD },
  293. std::forward<String1>(url), std::forward<String2>(filepath));
  294. }
  295. // ----------------------------------------------------------------------------------------
  296. /**
  297. * @brief blocking download the http file until it is returned on success or failure
  298. * @param url - The url of the file to download.
  299. * @param cbb - A function that circularly receives the contents of the file in chunks. void(std::string_view data)
  300. */
  301. template<class String1, class BodyCallback>
  302. typename std::enable_if_t<
  303. detail::can_convert_to_string_v<detail::remove_cvref_t<String1>> &&
  304. detail::is_callable_v<BodyCallback>, bool>
  305. static inline download(const asio::ssl::context& ctx, String1&& url, BodyCallback&& cbb)
  306. {
  307. http::web_request req = http::make_request(std::forward<String1>(url));
  308. if (get_last_error())
  309. return false;
  310. auto cbh = [](const auto&) {};
  311. return derived_t::download(ctx, req.host(), req.port(), req.base(), cbh, cbb, std::in_place);
  312. }
  313. /**
  314. * @brief blocking download the http file until it is returned on success or failure
  315. * @param req - The web_request which can be create by http::make_request(...)
  316. * @param cbb - A function that circularly receives the contents of the file in chunks. void(std::string_view data)
  317. */
  318. template<class BodyCallback>
  319. typename std::enable_if_t<detail::is_callable_v<BodyCallback>, bool>
  320. static inline download(const asio::ssl::context& ctx, http::web_request& req, BodyCallback&& cbb)
  321. {
  322. auto cbh = [](const auto&) {};
  323. return derived_t::download(ctx, req.host(), req.port(), req.base(), cbh, cbb, std::in_place);
  324. }
  325. /**
  326. * @brief blocking download the http file until it is returned on success or failure
  327. * @param req - The web_request which can be create by http::make_request(...)
  328. * @param cbb - A function that circularly receives the contents of the file in chunks. void(std::string_view data)
  329. */
  330. template<class BodyCallback>
  331. typename std::enable_if_t<detail::is_callable_v<BodyCallback>, bool>
  332. static inline download(http::web_request& req, BodyCallback&& cbb)
  333. {
  334. return derived_t::download(asio::ssl::context{ ASIO2_DEFAULT_SSL_METHOD }, req, cbb);
  335. }
  336. /**
  337. * @brief blocking download the http file until it is returned on success or failure
  338. * @param req - The web_request which can be create by http::make_request(...)
  339. * @param cbh - A function that recv the http response header message. void(auto& message)
  340. * @param cbb - A function that circularly receives the contents of the file in chunks. void(std::string_view data)
  341. */
  342. template<class HeaderCallback, class BodyCallback>
  343. typename std::enable_if_t<detail::is_callable_v<BodyCallback>, bool>
  344. static inline download(const asio::ssl::context& ctx, http::web_request& req, HeaderCallback&& cbh, BodyCallback&& cbb)
  345. {
  346. return derived_t::download(ctx, req.host(), req.port(), req.base(), cbh, cbb, std::in_place);
  347. }
  348. /**
  349. * @brief blocking download the http file until it is returned on success or failure
  350. * @param req - The web_request which can be create by http::make_request(...)
  351. * @param cbh - A function that recv the http response header message. void(auto& message)
  352. * @param cbb - A function that circularly receives the contents of the file in chunks. void(std::string_view data)
  353. */
  354. template<class HeaderCallback, class BodyCallback>
  355. typename std::enable_if_t<detail::is_callable_v<BodyCallback>, bool>
  356. static inline download(http::web_request& req, HeaderCallback&& cbh, BodyCallback&& cbb)
  357. {
  358. return derived_t::download(asio::ssl::context{ ASIO2_DEFAULT_SSL_METHOD }, req, cbh, cbb);
  359. }
  360. // ----------------------------------------------------------------------------------------
  361. /**
  362. * @brief blocking download the http file until it is returned on success or failure
  363. * @param url - The url of the file to download.
  364. * @param cbb - A function that circularly receives the contents of the file in chunks. void(std::string_view data)
  365. */
  366. template<class String1, class BodyCallback>
  367. typename std::enable_if_t<
  368. detail::can_convert_to_string_v<detail::remove_cvref_t<String1>> &&
  369. detail::is_callable_v<BodyCallback>, bool>
  370. static inline download(String1&& url, BodyCallback&& cbb)
  371. {
  372. return derived_t::download(asio::ssl::context{ ASIO2_DEFAULT_SSL_METHOD },
  373. std::forward<String1>(url), std::forward<BodyCallback>(cbb));
  374. }
  375. // ----------------------------------------------------------------------------------------
  376. /**
  377. * @brief blocking download the http file until it is returned on success or failure
  378. * @param url - The url of the file to download.
  379. * @param cbh - A function that recv the http response header message. void(auto& message)
  380. * @param cbb - A function that circularly receives the contents of the file in chunks. void(std::string_view data)
  381. */
  382. template<class String1, class HeaderCallback, class BodyCallback>
  383. typename std::enable_if_t<
  384. detail::can_convert_to_string_v<detail::remove_cvref_t<String1>> &&
  385. detail::is_callable_v<BodyCallback>, bool>
  386. static inline download(const asio::ssl::context& ctx, String1&& url, HeaderCallback&& cbh, BodyCallback&& cbb)
  387. {
  388. http::web_request req = http::make_request(std::forward<String1>(url));
  389. if (get_last_error())
  390. return false;
  391. return derived_t::download(ctx, req.host(), req.port(), req.base(), cbh, cbb, std::in_place);
  392. }
  393. // ----------------------------------------------------------------------------------------
  394. /**
  395. * @brief blocking download the http file until it is returned on success or failure
  396. * @param url - The url of the file to download.
  397. * @param cbh - A function that recv the http response header message. void(auto& message)
  398. * @param cbb - A function that circularly receives the contents of the file in chunks. void(std::string_view data)
  399. */
  400. template<class String1, class HeaderCallback, class BodyCallback>
  401. typename std::enable_if_t<
  402. detail::can_convert_to_string_v<detail::remove_cvref_t<String1>> &&
  403. detail::is_callable_v<BodyCallback>, bool>
  404. static inline download(String1&& url, HeaderCallback&& cbh, BodyCallback&& cbb)
  405. {
  406. return derived_t::download(asio::ssl::context{ ASIO2_DEFAULT_SSL_METHOD },
  407. std::forward<String1>(url), std::forward<HeaderCallback>(cbh), std::forward<BodyCallback>(cbb));
  408. }
  409. };
  410. template<class derived_t, class args_t = void>
  411. struct https_download_impl : public https_download_impl_bridge<derived_t, args_t> {};
  412. }
  413. #include <asio2/base/detail/pop_options.hpp>
  414. #endif // !__ASIO2_HTTPS_DOWNLOAD_HPP__
  415. #endif