/engine/interfaces/sc_http_curl.cpp

https://github.com/simulationcraft/simc · C++ · 327 lines · 255 code · 63 blank · 9 comment · 66 complexity · 5520f680062957a741c31c1fc9657ef4 MD5 · raw file

  1. #include "config.hpp"
  2. #ifdef SC_USE_CURL
  3. #include "sc_http_curl.hpp"
  4. #include "util/generic.hpp"
  5. #include "util/util.hpp"
  6. #include "fmt/format.h"
  7. #include <iostream>
  8. namespace
  9. {
  10. const std::string UA_STR = "Simulationcraft+CURL/" + std::string( SC_VERSION );
  11. } /* Namespace anonymous ends */
  12. namespace http
  13. {
  14. void curl_handle_t::set_proxy()
  15. {
  16. const auto& proxy = http::get_proxy();
  17. if ( m_handle == nullptr )
  18. {
  19. return;
  20. }
  21. bool ssl_proxy = util::str_compare_ci( proxy.type, "https" );
  22. bool use_proxy = ssl_proxy || util::str_compare_ci( proxy.type, "http" );
  23. if ( !use_proxy )
  24. {
  25. return;
  26. }
  27. // Note, won't cover the case where curl is compiled without USE_SSL
  28. #ifdef CURL_VERSION_HTTPS_PROXY
  29. auto ret = curl_easy_setopt( m_handle, CURLOPT_PROXYTYPE,
  30. ssl_proxy ? CURLPROXY_HTTPS : CURLPROXY_HTTP );
  31. #else
  32. if ( ssl_proxy )
  33. {
  34. throw std::runtime_error( "Libcurl does not support HTTPS proxies" );
  35. }
  36. auto ret = curl_easy_setopt( m_handle, CURLOPT_PROXYTYPE, CURLPROXY_HTTP );
  37. #endif /* CURL_VERSION_HTTPS_PROXY */
  38. if ( ret != CURLE_OK )
  39. {
  40. throw std::runtime_error( fmt::format( "Unable to setup proxy: {}", m_error ) );
  41. }
  42. if ( curl_easy_setopt( m_handle, CURLOPT_PROXY, proxy.host.c_str() ) != CURLE_OK )
  43. {
  44. throw std::runtime_error( fmt::format( "Unable to setup proxy: {}", m_error ) );
  45. }
  46. if ( curl_easy_setopt( m_handle, CURLOPT_PROXYPORT, proxy.port ) != CURLE_OK )
  47. {
  48. throw std::runtime_error( fmt::format( "Unable to setup proxy: {}", m_error ) );
  49. }
  50. }
  51. size_t curl_handle_t::read_data( const char* data_in, size_t total_bytes )
  52. {
  53. m_buffer.append( data_in, total_bytes );
  54. return total_bytes;
  55. }
  56. size_t curl_handle_t::add_response_header( const char* header_str, size_t total_bytes )
  57. {
  58. std::string header = std::string( header_str, total_bytes );
  59. // Find first ':'
  60. auto pos = header.find( ':' );
  61. // Don't support foldable headers since they are deprecated anyhow
  62. if ( pos == std::string::npos || header[ 0 ] == ' ' )
  63. {
  64. return total_bytes;
  65. }
  66. std::string key = header.substr( 0, pos );
  67. std::string value = header.substr( pos + 1 );
  68. // Transform all header keys to lowercase to sanitize the input
  69. std::transform( key.begin(), key.end(), key.begin(), tolower );
  70. // Prune whitespaces from values
  71. while ( !value.empty() && ( value.back() == ' ' || value.back() == '\n' ||
  72. value.back() == '\t' || value.back() == '\r' ) )
  73. {
  74. value.pop_back();
  75. }
  76. while ( !value.empty() && ( value.front() == ' ' || value.front() == '\t' ) )
  77. {
  78. value.erase( value.begin() );
  79. }
  80. m_headers[ key ] = value;
  81. return total_bytes;
  82. }
  83. size_t curl_handle_t::data_cb( void* contents, size_t size, size_t nmemb, void* usr )
  84. {
  85. curl_handle_t* obj = reinterpret_cast<curl_handle_t*>( usr );
  86. return obj->read_data( reinterpret_cast<const char*>( contents ), size * nmemb );
  87. }
  88. size_t curl_handle_t::header_cb( char* contents, size_t size, size_t nmemb, void* usr )
  89. {
  90. curl_handle_t* obj = reinterpret_cast<curl_handle_t*>( usr );
  91. return obj->add_response_header( contents, size * nmemb );
  92. }
  93. curl_handle_t::curl_handle_t() : http_handle_t(), m_handle( nullptr ), m_header_opts( nullptr )
  94. {
  95. m_error[ 0 ] = '\0';
  96. }
  97. curl_handle_t::curl_handle_t( CURL* handle ) :
  98. http_handle_t(), m_handle( handle ), m_header_opts( nullptr )
  99. {
  100. m_error[ 0 ] = '\0';
  101. auto ret = curl_easy_setopt( handle, CURLOPT_ERRORBUFFER, m_error );
  102. if ( ret != CURLE_OK )
  103. {
  104. throw std::runtime_error( fmt::format( "Unable to setup CURL: {}", curl_easy_strerror( ret ) ) );
  105. }
  106. if ( curl_easy_setopt( handle, CURLOPT_TIMEOUT, 15L ) != CURLE_OK )
  107. {
  108. throw std::runtime_error( fmt::format( "Unable to setup CURL: {}", m_error ) );
  109. }
  110. if ( curl_easy_setopt( handle, CURLOPT_FOLLOWLOCATION, 1L ) != CURLE_OK )
  111. {
  112. throw std::runtime_error( fmt::format( "Unable to setup CURL: {}", m_error ) );
  113. }
  114. if ( curl_easy_setopt( handle, CURLOPT_MAXREDIRS, 5L ) != CURLE_OK )
  115. {
  116. throw std::runtime_error( fmt::format( "Unable to setup CURL: {}", m_error ) );
  117. }
  118. if ( curl_easy_setopt( handle, CURLOPT_ACCEPT_ENCODING, "" ) != CURLE_OK )
  119. {
  120. throw std::runtime_error( fmt::format( "Unable to setup CURL: {}", m_error ) );
  121. }
  122. if ( curl_easy_setopt( handle, CURLOPT_USERAGENT, UA_STR.c_str() ) != CURLE_OK )
  123. {
  124. throw std::runtime_error( fmt::format( "Unable to setup CURL: {}", m_error ) );
  125. }
  126. if ( curl_easy_setopt( handle, CURLOPT_HEADERFUNCTION, header_cb ) != CURLE_OK )
  127. {
  128. throw std::runtime_error( fmt::format( "Unable to setup CURL: {}", m_error ) );
  129. }
  130. if ( curl_easy_setopt( handle, CURLOPT_WRITEFUNCTION, data_cb ) != CURLE_OK )
  131. {
  132. throw std::runtime_error( fmt::format( "Unable to setup CURL: {}", m_error ) );
  133. }
  134. if ( curl_easy_setopt( handle, CURLOPT_HEADERDATA, reinterpret_cast<void*>( this ) ) != CURLE_OK )
  135. {
  136. throw std::runtime_error( fmt::format( "Unable to setup CURL: {}", m_error ) );
  137. }
  138. if ( curl_easy_setopt( handle, CURLOPT_WRITEDATA, reinterpret_cast<void*>( this ) ) != CURLE_OK )
  139. {
  140. throw std::runtime_error( fmt::format( "Unable to setup CURL: {}", m_error ) );
  141. }
  142. #ifdef CURL_DEBUG
  143. if ( curl_easy_setopt( handle, CURLOPT_VERBOSE, 1L ) != CURLE_OK )
  144. {
  145. throw std::runtime_error( fmt::format( "Unable to setup CURL: {}", m_error ) );
  146. }
  147. #endif
  148. set_proxy();
  149. }
  150. curl_handle_t::~curl_handle_t()
  151. {
  152. curl_easy_reset( m_handle );
  153. curl_slist_free_all( m_header_opts );
  154. }
  155. bool curl_handle_t::initialized() const
  156. {
  157. return m_handle != nullptr;
  158. }
  159. // Curl descriptive error (when fetch() returns != CURLE_OK)
  160. std::string curl_handle_t::error() const
  161. {
  162. return { m_error };
  163. }
  164. // Response body
  165. const std::string& curl_handle_t::result() const
  166. {
  167. return m_buffer;
  168. }
  169. // Response headers
  170. const std::unordered_map<std::string, std::string>& curl_handle_t::headers() const
  171. {
  172. return m_headers;
  173. }
  174. void curl_handle_t::add_request_headers( const std::vector<std::string>& headers )
  175. {
  176. range::for_each( headers, [ this ]( const std::string& header ) {
  177. m_header_opts = curl_slist_append( m_header_opts, header.c_str() );
  178. } );
  179. curl_easy_setopt( m_handle, CURLOPT_HTTPHEADER, m_header_opts );
  180. }
  181. void curl_handle_t::add_request_header( const std::string& header )
  182. {
  183. m_header_opts = curl_slist_append( m_header_opts, header.c_str() );
  184. curl_easy_setopt( m_handle, CURLOPT_HTTPHEADER, m_header_opts );
  185. }
  186. bool curl_handle_t::set_basic_auth( const std::string& userpwd )
  187. {
  188. return curl_easy_setopt( m_handle, CURLOPT_USERPWD, userpwd.c_str() ) == CURLE_OK;
  189. }
  190. bool curl_handle_t::set_basic_auth( const std::string& user, const std::string& pwd )
  191. {
  192. return set_basic_auth( user + ":" + pwd );
  193. }
  194. // Fetch an url
  195. bool curl_handle_t::get( const std::string& url )
  196. {
  197. if ( url.empty() )
  198. {
  199. return false;
  200. }
  201. if ( curl_easy_setopt( m_handle, CURLOPT_URL, url.c_str() ) != CURLE_OK )
  202. {
  203. std::cerr << fmt::format( "Unable to set URL {}: {}", url, m_error ) << std::endl;
  204. return false;
  205. }
  206. if ( curl_easy_perform( m_handle ) != CURLE_OK )
  207. {
  208. std::cerr << fmt::format( "Unable to perform operation: {}", m_error ) << std::endl;
  209. return false;
  210. }
  211. return true;
  212. }
  213. bool curl_handle_t::post( const std::string& url, const std::string& data, const std::string& content_type )
  214. {
  215. if ( !content_type.empty() )
  216. {
  217. add_request_header( "Content-Type: " + content_type );
  218. }
  219. if ( curl_easy_setopt( m_handle, CURLOPT_POSTFIELDS, data.c_str() ) != CURLE_OK )
  220. {
  221. std::cerr << fmt::format( "Unable to set POST data \"{}\": {}", data, m_error ) << std::endl;
  222. return false;
  223. }
  224. return get( url );
  225. }
  226. int curl_handle_t::response_code() const
  227. {
  228. long response_code = 0;
  229. if ( curl_easy_getinfo( m_handle, CURLINFO_RESPONSE_CODE, &response_code ) != CURLE_OK )
  230. {
  231. std::cerr << fmt::format( "Unable to retrieve response code: {}", m_error ) << std::endl;
  232. return -1;
  233. }
  234. return as<int>( response_code );
  235. }
  236. curl_connection_pool_t::curl_connection_pool_t() : http_connection_pool_t(), m_handle( nullptr )
  237. {
  238. auto ret = curl_global_init( CURL_GLOBAL_ALL );
  239. if ( ret != CURLE_OK )
  240. {
  241. throw std::runtime_error( fmt::format( "Unable to initialize libcurl: {}",
  242. curl_easy_strerror( ret ) ) );
  243. }
  244. }
  245. curl_connection_pool_t::~curl_connection_pool_t()
  246. {
  247. curl_easy_cleanup( m_handle );
  248. curl_global_cleanup();
  249. }
  250. std::unique_ptr<http_handle_t> curl_connection_pool_t::handle( const std::string& /* url */ )
  251. {
  252. if ( m_handle )
  253. {
  254. return std::unique_ptr<http_handle_t>( new curl_handle_t( m_handle ) );
  255. }
  256. m_handle = curl_easy_init();
  257. if ( !m_handle )
  258. {
  259. throw std::runtime_error( "Unable to create CURL handle" );
  260. }
  261. return std::unique_ptr<http_handle_t>( new curl_handle_t( m_handle ) );
  262. }
  263. } /* Namespace http ends */
  264. #endif /* SC_USE_CURL */