connection.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561
  1. /**
  2. * @file connection.cpp
  3. * @brief implementation of the connection class
  4. * @author Daniel Schauenberg <d@unwiredcouch.com>
  5. */
  6. #include "restclient-cpp/connection.h"
  7. #include <curl/curl.h>
  8. #include <cstring>
  9. #include <string>
  10. #include <iostream>
  11. #include <map>
  12. #include <stdexcept>
  13. #include <utility>
  14. #include "restclient-cpp/restclient.h"
  15. #include "restclient-cpp/helpers.h"
  16. /**
  17. * @brief constructor for the Connection object
  18. *
  19. * @param baseUrl - base URL for the connection to use
  20. *
  21. */
  22. RestClient::Connection::Connection(const std::string& baseUrl)
  23. : lastRequest(), headerFields() {
  24. this->curlHandle = curl_easy_init();
  25. if (!this->curlHandle) {
  26. throw std::runtime_error("Couldn't initialize curl handle");
  27. }
  28. this->baseUrl = baseUrl;
  29. this->timeout = 0;
  30. this->followRedirects = false;
  31. this->maxRedirects = -1l;
  32. this->noSignal = false;
  33. }
  34. RestClient::Connection::~Connection() {
  35. if (this->curlHandle) {
  36. curl_easy_cleanup(this->curlHandle);
  37. }
  38. }
  39. // getters/setters
  40. /**
  41. * @brief get diagnostic information about the connection object
  42. *
  43. * @return RestClient::Connection::Info struct
  44. */
  45. RestClient::Connection::Info
  46. RestClient::Connection::GetInfo() {
  47. RestClient::Connection::Info ret;
  48. ret.baseUrl = this->baseUrl;
  49. ret.headers = this->GetHeaders();
  50. ret.timeout = this->timeout;
  51. ret.followRedirects = this->followRedirects;
  52. ret.maxRedirects = this->maxRedirects;
  53. ret.noSignal = this->noSignal;
  54. ret.basicAuth.username = this->basicAuth.username;
  55. ret.basicAuth.password = this->basicAuth.password;
  56. ret.customUserAgent = this->customUserAgent;
  57. ret.lastRequest = this->lastRequest;
  58. ret.certPath = this->certPath;
  59. ret.certType = this->certType;
  60. ret.keyPath = this->keyPath;
  61. ret.keyPassword = this->keyPassword;
  62. ret.uriProxy = this->uriProxy;
  63. return ret;
  64. }
  65. /**
  66. * @brief append a header to the internal map
  67. *
  68. * @param key for the header field
  69. * @param value for the header field
  70. *
  71. */
  72. void
  73. RestClient::Connection::AppendHeader(const std::string& key,
  74. const std::string& value) {
  75. this->headerFields[key] = value;
  76. }
  77. /**
  78. * @brief set the custom headers map. This will replace the currently
  79. * configured headers with the provided ones. If you want to add additional
  80. * headers, use AppendHeader()
  81. *
  82. * @param headers to set
  83. */
  84. void
  85. RestClient::Connection::SetHeaders(RestClient::HeaderFields headers) {
  86. #if __cplusplus >= 201103L
  87. this->headerFields = std::move(headers);
  88. #else
  89. this->headerFields = headers;
  90. #endif
  91. }
  92. /**
  93. * @brief get all custom headers set on the connection
  94. *
  95. * @returns a RestClient::HeaderFields map containing the custom headers
  96. */
  97. RestClient::HeaderFields
  98. RestClient::Connection::GetHeaders() {
  99. return this->headerFields;
  100. }
  101. /**
  102. * @brief configure whether to follow redirects on this connection
  103. *
  104. * @param follow - boolean whether to follow redirects
  105. */
  106. void
  107. RestClient::Connection::FollowRedirects(bool follow) {
  108. this->followRedirects = follow;
  109. this->maxRedirects = -1l;
  110. }
  111. /**
  112. * @brief configure whether to follow redirects on this connection
  113. *
  114. * @param follow - boolean whether to follow redirects
  115. * @param maxRedirects - int indicating the maximum number of redirect to follow (-1 unlimited)
  116. */
  117. void
  118. RestClient::Connection::FollowRedirects(bool follow, int maxRedirects) {
  119. this->followRedirects = follow;
  120. this->maxRedirects = maxRedirects;
  121. }
  122. /**
  123. * @brief set custom user agent for connection. This gets prepended to the
  124. * default restclient-cpp/RESTCLIENT_VERSION string
  125. *
  126. * @param userAgent - custom userAgent prefix
  127. *
  128. */
  129. void
  130. RestClient::Connection::SetUserAgent(const std::string& userAgent) {
  131. this->customUserAgent = userAgent;
  132. }
  133. /**
  134. * @brief set custom Certificate Authority (CA) path
  135. *
  136. * @param caInfoFilePath - The path to a file holding the certificates used to
  137. * verify the peer with. See CURLOPT_CAINFO
  138. *
  139. */
  140. void
  141. RestClient::Connection::SetCAInfoFilePath(const std::string& caInfoFilePath) {
  142. this->caInfoFilePath = caInfoFilePath;
  143. }
  144. /**
  145. * @brief get the user agent to add to the request
  146. *
  147. * @return user agent as std::string
  148. */
  149. std::string
  150. RestClient::Connection::GetUserAgent() {
  151. std::string prefix;
  152. if (this->customUserAgent.length() > 0) {
  153. prefix = this->customUserAgent + " ";
  154. }
  155. return std::string(prefix + "");
  156. }
  157. /**
  158. * @brief set timeout for connection
  159. *
  160. * @param seconds - timeout in seconds
  161. *
  162. */
  163. void
  164. RestClient::Connection::SetTimeout(int seconds) {
  165. this->timeout = seconds;
  166. }
  167. /**
  168. * @brief switch off curl signals for connection (see CURLOPT_NONSIGNAL). By
  169. * default signals are used, except when timeout is given.
  170. *
  171. * @param no - set to true switches signals off
  172. *
  173. */
  174. void
  175. RestClient::Connection::SetNoSignal(bool no) {
  176. this->noSignal = no;
  177. }
  178. /**
  179. * @brief set username and password for basic auth
  180. *
  181. * @param username
  182. * @param password
  183. *
  184. */
  185. void
  186. RestClient::Connection::SetBasicAuth(const std::string& username,
  187. const std::string& password) {
  188. this->basicAuth.username = username;
  189. this->basicAuth.password = password;
  190. }
  191. /**
  192. * @brief set certificate path
  193. *
  194. * @param path to certificate file
  195. *
  196. */
  197. void
  198. RestClient::Connection::SetCertPath(const std::string& cert) {
  199. this->certPath = cert;
  200. }
  201. /**
  202. * @brief set certificate type
  203. *
  204. * @param certificate type (e.g. "PEM" or "DER")
  205. *
  206. */
  207. void
  208. RestClient::Connection::SetCertType(const std::string& certType) {
  209. this->certType = certType;
  210. }
  211. /**
  212. * @brief set key path
  213. *
  214. * @param path to key file
  215. *
  216. */
  217. void
  218. RestClient::Connection::SetKeyPath(const std::string& keyPath) {
  219. this->keyPath = keyPath;
  220. }
  221. /**
  222. * @brief set key password
  223. *
  224. * @param key password
  225. *
  226. */
  227. void
  228. RestClient::Connection::SetKeyPassword(const std::string& keyPassword) {
  229. this->keyPassword = keyPassword;
  230. }
  231. /**
  232. * @brief set HTTP proxy address and port
  233. *
  234. * @param proxy address with port number
  235. *
  236. */
  237. void
  238. RestClient::Connection::SetProxy(const std::string& uriProxy) {
  239. std::string uriProxyUpper = uriProxy;
  240. // check if the provided address is prefixed with "http"
  241. std::transform(uriProxyUpper.begin(), uriProxyUpper.end(),
  242. uriProxyUpper.begin(), ::toupper);
  243. if ((uriProxy.length() > 0) && (uriProxyUpper.compare(0, 4, "HTTP") != 0)) {
  244. this->uriProxy = "http://" + uriProxy;
  245. } else {
  246. this->uriProxy = uriProxy;
  247. }
  248. }
  249. /**
  250. * @brief helper function to get called from the actual request methods to
  251. * prepare the curlHandle for transfer with generic options, perform the
  252. * request and record some stats from the last request and then reset the
  253. * handle with curl_easy_reset to its default state. This will keep things
  254. * like connections and session ID intact but makes sure you can change
  255. * parameters on the object for another request.
  256. *
  257. * @param uri URI to query
  258. * @param ret Reference to the Response struct that should be filled
  259. *
  260. * @return 0 on success and 1 on error
  261. */
  262. RestClient::Response
  263. RestClient::Connection::performCurlRequest(const std::string& uri, const std::string method_type) {
  264. // init return type
  265. RestClient::Response ret = {};
  266. std::string url = std::string(this->baseUrl + uri);
  267. std::string headerString;
  268. CURLcode res = CURLE_OK;
  269. curl_slist* headerList = NULL;
  270. /** set query URL */
  271. curl_easy_setopt(this->curlHandle, CURLOPT_URL, url.c_str());
  272. if (method_type.compare("download") != 0) {
  273. /** set callback function */
  274. curl_easy_setopt(this->curlHandle, CURLOPT_WRITEFUNCTION,
  275. Helpers::write_callback);
  276. /** set data object to pass to callback function */
  277. curl_easy_setopt(this->curlHandle, CURLOPT_WRITEDATA, &ret);
  278. }
  279. /** set the header callback function */
  280. curl_easy_setopt(this->curlHandle, CURLOPT_HEADERFUNCTION,
  281. Helpers::header_callback);
  282. /** callback object for headers */
  283. curl_easy_setopt(this->curlHandle, CURLOPT_HEADERDATA, &ret);
  284. /** set http headers */
  285. for (HeaderFields::const_iterator it = this->headerFields.begin();
  286. it != this->headerFields.end(); ++it) {
  287. headerString = it->first;
  288. headerString += ": ";
  289. headerString += it->second;
  290. headerList = curl_slist_append(headerList, headerString.c_str());
  291. }
  292. curl_easy_setopt(this->curlHandle, CURLOPT_HTTPHEADER,
  293. headerList);
  294. // set basic auth if configured
  295. if (this->basicAuth.username.length() > 0) {
  296. std::string authString = std::string(this->basicAuth.username + ":" +
  297. this->basicAuth.password);
  298. curl_easy_setopt(this->curlHandle, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
  299. curl_easy_setopt(this->curlHandle, CURLOPT_USERPWD, authString.c_str());
  300. }
  301. /** set user agent */
  302. curl_easy_setopt(this->curlHandle, CURLOPT_USERAGENT,
  303. this->GetUserAgent().c_str());
  304. // set timeout
  305. if (this->timeout) {
  306. curl_easy_setopt(this->curlHandle, CURLOPT_TIMEOUT, this->timeout);
  307. // dont want to get a sig alarm on timeout
  308. curl_easy_setopt(this->curlHandle, CURLOPT_NOSIGNAL, 1);
  309. }
  310. // set follow redirect
  311. if (this->followRedirects == true) {
  312. curl_easy_setopt(this->curlHandle, CURLOPT_FOLLOWLOCATION, 1L);
  313. curl_easy_setopt(this->curlHandle, CURLOPT_MAXREDIRS,
  314. static_cast<int64_t>(this->maxRedirects));
  315. }
  316. if (this->noSignal) {
  317. // multi-threaded and prevent entering foreign signal handler (e.g. JNI)
  318. curl_easy_setopt(this->curlHandle, CURLOPT_NOSIGNAL, 1);
  319. }
  320. // if provided, supply CA path
  321. if (!this->caInfoFilePath.empty()) {
  322. curl_easy_setopt(this->curlHandle, CURLOPT_CAINFO,
  323. this->caInfoFilePath.c_str());
  324. }
  325. // set cert file path
  326. if (!this->certPath.empty()) {
  327. curl_easy_setopt(this->curlHandle, CURLOPT_SSLCERT,
  328. this->certPath.c_str());
  329. }
  330. // set cert type
  331. if (!this->certType.empty()) {
  332. curl_easy_setopt(this->curlHandle, CURLOPT_SSLCERTTYPE,
  333. this->certType.c_str());
  334. }
  335. // set key file path
  336. if (!this->keyPath.empty()) {
  337. curl_easy_setopt(this->curlHandle, CURLOPT_SSLKEY,
  338. this->keyPath.c_str());
  339. }
  340. // set key password
  341. if (!this->keyPassword.empty()) {
  342. curl_easy_setopt(this->curlHandle, CURLOPT_KEYPASSWD,
  343. this->keyPassword.c_str());
  344. }
  345. // set web proxy address
  346. if (!this->uriProxy.empty()) {
  347. curl_easy_setopt(this->curlHandle, CURLOPT_PROXY,
  348. uriProxy.c_str());
  349. curl_easy_setopt(this->curlHandle, CURLOPT_HTTPPROXYTUNNEL,
  350. 1L);
  351. }
  352. res = curl_easy_perform(this->curlHandle);
  353. if (res != CURLE_OK) {
  354. switch (res) {
  355. case CURLE_OPERATION_TIMEDOUT:
  356. ret.code = res;
  357. ret.body = "Operation Timeout.";
  358. break;
  359. case CURLE_SSL_CERTPROBLEM:
  360. ret.code = res;
  361. ret.body = curl_easy_strerror(res);
  362. break;
  363. default:
  364. ret.body = "Failed to query.";
  365. ret.body += curl_easy_strerror(res);
  366. ret.code = -1;
  367. }
  368. } else {
  369. int64_t http_code = 0;
  370. curl_easy_getinfo(this->curlHandle, CURLINFO_RESPONSE_CODE, &http_code);
  371. ret.code = static_cast<int>(http_code);
  372. }
  373. curl_easy_getinfo(this->curlHandle, CURLINFO_TOTAL_TIME,
  374. &this->lastRequest.totalTime);
  375. curl_easy_getinfo(this->curlHandle, CURLINFO_NAMELOOKUP_TIME,
  376. &this->lastRequest.nameLookupTime);
  377. curl_easy_getinfo(this->curlHandle, CURLINFO_CONNECT_TIME,
  378. &this->lastRequest.connectTime);
  379. curl_easy_getinfo(this->curlHandle, CURLINFO_APPCONNECT_TIME,
  380. &this->lastRequest.appConnectTime);
  381. curl_easy_getinfo(this->curlHandle, CURLINFO_PRETRANSFER_TIME,
  382. &this->lastRequest.preTransferTime);
  383. curl_easy_getinfo(this->curlHandle, CURLINFO_STARTTRANSFER_TIME,
  384. &this->lastRequest.startTransferTime);
  385. curl_easy_getinfo(this->curlHandle, CURLINFO_REDIRECT_TIME,
  386. &this->lastRequest.redirectTime);
  387. curl_easy_getinfo(this->curlHandle, CURLINFO_REDIRECT_COUNT,
  388. &this->lastRequest.redirectCount);
  389. // free header list
  390. curl_slist_free_all(headerList);
  391. // reset curl handle
  392. curl_easy_reset(this->curlHandle);
  393. return ret;
  394. }
  395. /**
  396. * @brief HTTP GET method
  397. *
  398. * @param url to query
  399. *
  400. * @return response struct
  401. */
  402. RestClient::Response
  403. RestClient::Connection::get(const std::string& url) {
  404. return this->performCurlRequest(url);
  405. }
  406. /**
  407. * @brief HTTP POST method
  408. *
  409. * @param url to query
  410. * @param data HTTP POST body
  411. *
  412. * @return response struct
  413. */
  414. RestClient::Response
  415. RestClient::Connection::post(const std::string& url,
  416. const std::string& data) {
  417. /** Now specify we want to POST data */
  418. curl_easy_setopt(this->curlHandle, CURLOPT_POST, 1L);
  419. /** set post fields */
  420. curl_easy_setopt(this->curlHandle, CURLOPT_POSTFIELDS, data.c_str());
  421. curl_easy_setopt(this->curlHandle, CURLOPT_POSTFIELDSIZE, data.size());
  422. return this->performCurlRequest(url);
  423. }
  424. /**
  425. * @brief HTTP PUT method
  426. *
  427. * @param url to query
  428. * @param data HTTP PUT body
  429. *
  430. * @return response struct
  431. */
  432. RestClient::Response
  433. RestClient::Connection::put(const std::string& url,
  434. const std::string& data) {
  435. /** initialize upload object */
  436. RestClient::Helpers::UploadObject up_obj;
  437. up_obj.data = data.c_str();
  438. up_obj.length = data.size();
  439. /** Now specify we want to PUT data */
  440. curl_easy_setopt(this->curlHandle, CURLOPT_PUT, 1L);
  441. curl_easy_setopt(this->curlHandle, CURLOPT_UPLOAD, 1L);
  442. /** set read callback function */
  443. curl_easy_setopt(this->curlHandle, CURLOPT_READFUNCTION,
  444. RestClient::Helpers::read_callback);
  445. /** set data object to pass to callback function */
  446. curl_easy_setopt(this->curlHandle, CURLOPT_READDATA, &up_obj);
  447. /** set data size */
  448. curl_easy_setopt(this->curlHandle, CURLOPT_INFILESIZE,
  449. static_cast<int64_t>(up_obj.length));
  450. return this->performCurlRequest(url);
  451. }
  452. /**
  453. * @brief HTTP DELETE method
  454. *
  455. * @param url to query
  456. *
  457. * @return response struct
  458. */
  459. RestClient::Response
  460. RestClient::Connection::del(const std::string& url) {
  461. /** we want HTTP DELETE */
  462. const char* http_delete = "DELETE";
  463. /** set HTTP DELETE METHOD */
  464. curl_easy_setopt(this->curlHandle, CURLOPT_CUSTOMREQUEST, http_delete);
  465. return this->performCurlRequest(url);
  466. }
  467. /**
  468. * @brief HTTP HEAD method
  469. *
  470. * @param url to query
  471. *
  472. * @return response struct
  473. */
  474. RestClient::Response
  475. RestClient::Connection::head(const std::string& url) {
  476. /** we want HTTP HEAD */
  477. const char* http_head = "HEAD";
  478. /** set HTTP HEAD METHOD */
  479. curl_easy_setopt(this->curlHandle, CURLOPT_CUSTOMREQUEST, http_head);
  480. curl_easy_setopt(this->curlHandle, CURLOPT_NOBODY, 1L);
  481. return this->performCurlRequest(url);
  482. }
  483. /**
  484. * @brief HTTP GET method
  485. *
  486. * @param url to query
  487. *
  488. * @return response struct
  489. */
  490. RestClient::Response
  491. RestClient::Connection::download(const std::string& uri,
  492. const std::string& file_to_save) {
  493. Response ret;
  494. FILE* fp = fopen(file_to_save.c_str(), "wb");
  495. if (!fp) {
  496. LOGD("!!! Failed to create file on the disk\n");
  497. ret.body = "!!! Failed to create file on the disk";
  498. return ret;
  499. }
  500. /** set callback function */
  501. curl_easy_setopt(this->curlHandle, CURLOPT_WRITEFUNCTION,
  502. Helpers::download_callback);
  503. /** set data object to pass to callback function */
  504. curl_easy_setopt(this->curlHandle, CURLOPT_WRITEDATA, fp);
  505. ret = this->performCurlRequest(uri, "download");
  506. fflush(fp);
  507. fclose(fp);
  508. return ret;
  509. }