Discord library in plain C
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

requests.c 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596
  1. /*
  2. * requests.c -- librequests: libcurl wrapper implementation
  3. *
  4. * The MIT License (MIT)
  5. *
  6. * Copyright (c) 2014 Mark Mossberg <mark.mossberg@gmail.com>
  7. *
  8. * Permission is hereby granted, free of charge, to any person obtaining a copy
  9. * of this software and associated documentation files (the "Software"), to deal
  10. * in the Software without restriction, including without limitation the rights
  11. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  12. * copies of the Software, and to permit persons to whom the Software is
  13. * furnished to do so, subject to the following conditions:
  14. *
  15. * The above copyright notice and this permission notice shall be included in
  16. * all copies or substantial portions of the Software.
  17. *
  18. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  19. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  20. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  21. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  22. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  23. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  24. * THE SOFTWARE.
  25. */
  26. #include "requests.h"
  27. static int IS_FIRST = 1;
  28. /*
  29. * Prototypes
  30. */
  31. static void common_opt(CURL *curl, req_t *req);
  32. static char *user_agent(void);
  33. static int check_ok(long code);
  34. static CURLcode requests_pt(CURL *curl, req_t *req, char *url, char *data,
  35. char **custom_hdrv, int custom_hdrc, int put_flag);
  36. static int hdrv_append(char ***hdrv, int *hdrc, char *new);
  37. static CURLcode process_custom_headers(struct curl_slist **slist,
  38. req_t *req, char **custom_hdrv,
  39. int custom_hdrc);
  40. /*
  41. * requests_init - Initializes requests struct data members
  42. *
  43. * Returns libcurl handle on success, or NULL on failure.
  44. *
  45. * @req: reference to req_t to be initialized
  46. */
  47. CURL *requests_init(req_t *req)
  48. {
  49. /* if this is not their first request, free previous memory */
  50. if (!IS_FIRST)
  51. requests_close(req);
  52. req->code = 0;
  53. req->url = NULL;
  54. req->size = 0;
  55. req->req_hdrc = 0;
  56. req->resp_hdrc = 0;
  57. req->ok = -1;
  58. req->text = calloc(1, 1);
  59. if (req->text == NULL)
  60. return NULL;
  61. req->req_hdrv = calloc(1, 1);
  62. if (req->req_hdrv == NULL)
  63. return NULL;
  64. req->resp_hdrv = calloc(1, 1);
  65. if (req->resp_hdrv == NULL)
  66. return NULL;
  67. IS_FIRST = 0;
  68. return curl_easy_init();
  69. }
  70. /*
  71. * requests_close - Calls curl clean up and free allocated memory
  72. *
  73. * @req: requests struct
  74. */
  75. void requests_close(req_t *req)
  76. {
  77. for (int i = 0; i < req->resp_hdrc; i++)
  78. free(req->resp_hdrv[i]);
  79. for (int i = 0; i < req->req_hdrc; i++)
  80. free(req->req_hdrv[i]);
  81. free(req->text);
  82. free(req->resp_hdrv);
  83. free(req->req_hdrv);
  84. IS_FIRST = 1;
  85. }
  86. /*
  87. * resp_callback - Callback function for requests, may be called multiple
  88. * times per request. Allocates memory and assembles response data.
  89. *
  90. * Note: `content' will not be NULL terminated.
  91. */
  92. static size_t resp_callback(char *content, size_t size, size_t nmemb,
  93. req_t *userdata)
  94. {
  95. size_t real_size = size * nmemb;
  96. /* extra 1 is for NULL terminator */
  97. userdata->text = realloc(userdata->text, userdata->size + real_size + 1);
  98. if (userdata->text == NULL)
  99. return -1;
  100. userdata->size += real_size;
  101. /* create NULL terminated version of `content' */
  102. char *responsetext = strndup(content, real_size + 1);
  103. if (responsetext == NULL)
  104. return -1;
  105. strncat(userdata->text, responsetext, real_size);
  106. free(responsetext);
  107. return real_size;
  108. }
  109. /*
  110. * header_callback - Callback function for headers, called once for each
  111. * header. Allocates memory and assembles headers into string array.
  112. *
  113. * Note: `content' will not be NULL terminated.
  114. */
  115. static size_t header_callback(char *content, size_t size, size_t nmemb,
  116. req_t *userdata)
  117. {
  118. size_t real_size = size * nmemb;
  119. /* the last header is always "\r\n" which we'll intentionally skip */
  120. if (strcmp(content, "\r\n") == 0)
  121. return real_size;
  122. if (hdrv_append(&userdata->resp_hdrv, &userdata->resp_hdrc, content))
  123. return -1;
  124. return real_size;
  125. }
  126. /*
  127. * requests_get - Performs GET request and populates req struct text member
  128. * with request response, code with response code, and size with size of
  129. * response.
  130. *
  131. * Returns the CURLcode return code provided from curl_easy_perform. CURLE_OK
  132. * is returned on success.
  133. *
  134. * @curl: libcurl handle
  135. * @req: request struct
  136. * @url: url to send request to
  137. */
  138. CURLcode requests_get(CURL *curl, req_t *req, char *url)
  139. {
  140. CURLcode rc;
  141. char *ua = user_agent();
  142. req->url = url;
  143. long code;
  144. common_opt(curl, req);
  145. curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, resp_callback);
  146. curl_easy_setopt(curl, CURLOPT_WRITEDATA, req);
  147. curl_easy_setopt(curl, CURLOPT_USERAGENT, ua);
  148. rc = curl_easy_perform(curl);
  149. if (rc != CURLE_OK)
  150. return rc;
  151. curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code);
  152. req->code = code;
  153. req->ok = check_ok(code);
  154. curl_easy_cleanup(curl);
  155. free(ua);
  156. return rc;
  157. }
  158. /*
  159. * requests_get_headers - Performs GET request (same as above) but allows
  160. * custom headers.
  161. *
  162. * Returns the CURLcode return code provided from curl_easy_perform. CURLE_OK
  163. * is returned on success.
  164. *
  165. * @curl: libcurl handle
  166. * @req: request struct
  167. * @url: url to send request to
  168. * @custom_hdrv: char* array of custom headers
  169. * @custom_hdrc: length of `custom_hdrv`
  170. */
  171. CURLcode requests_get_headers(CURL *curl, req_t *req, char *url,
  172. char **custom_hdrv, int custom_hdrc)
  173. {
  174. CURLcode rc;
  175. struct curl_slist *slist = NULL;
  176. char *ua = user_agent();
  177. req->url = url;
  178. long code;
  179. /* headers */
  180. if (custom_hdrv != NULL) {
  181. rc = process_custom_headers(&slist, req, custom_hdrv, custom_hdrc);
  182. if (rc != CURLE_OK)
  183. return rc;
  184. curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist);
  185. }
  186. common_opt(curl, req);
  187. curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, resp_callback);
  188. curl_easy_setopt(curl, CURLOPT_WRITEDATA, req);
  189. curl_easy_setopt(curl, CURLOPT_USERAGENT, ua);
  190. rc = curl_easy_perform(curl);
  191. if (rc != CURLE_OK)
  192. return rc;
  193. curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code);
  194. req->code = code;
  195. if (slist != NULL)
  196. curl_slist_free_all(slist);
  197. req->ok = check_ok(code);
  198. curl_easy_cleanup(curl);
  199. free(ua);
  200. return rc;
  201. }
  202. /*
  203. * requests_url_encode - Url encoding function. Takes as input an array of
  204. * char strings and the size of the array. The array should consist of keys
  205. * and the corresponding value immediately after in the array. There must be
  206. * an even number of array elements (one value for every key).
  207. *
  208. * Returns pointer to url encoded string if successful, or NULL if
  209. * unsuccessful.
  210. *
  211. * @curl: libcurl handle
  212. * @data: char* array as described above
  213. * @data-size: length of array
  214. */
  215. char *requests_url_encode(CURL *curl, char **data, int data_size)
  216. {
  217. char *key, *val, *tmp;
  218. int offset;
  219. size_t term_size;
  220. size_t tmp_len;
  221. if (data_size % 2 != 0)
  222. return NULL;
  223. /* loop through and get total sum of lengths */
  224. size_t total_size = 0;
  225. for (int i = 0; i < data_size; i++) {
  226. tmp = data[i];
  227. tmp_len = strlen(tmp);
  228. total_size += tmp_len;
  229. }
  230. char encoded[total_size]; /* clear junk bytes */
  231. snprintf(encoded, total_size, "%s", "");
  232. /* loop in groups of two, assembling key/val pairs */
  233. for (int i = 0; i < data_size; i+=2) {
  234. key = data[i];
  235. val = data[i+1];
  236. offset = i == 0 ? 2 : 3; /* =, \0 and maybe & */
  237. term_size = strlen(key) + strlen(val) + offset;
  238. char term[term_size];
  239. if (i == 0)
  240. snprintf(term, term_size, "%s=%s", key, val);
  241. else
  242. snprintf(term, term_size, "&%s=%s", key, val);
  243. strncat(encoded, term, strlen(term));
  244. }
  245. char *full_encoded = curl_easy_escape(curl, encoded, strlen(encoded));
  246. return full_encoded;
  247. }
  248. CURLcode requests_post(CURL *curl, req_t *req, char *url, char *data)
  249. {
  250. return requests_pt(curl, req, url, data, NULL, 0, 0);
  251. }
  252. CURLcode requests_put(CURL *curl, req_t *req, char *url, char *data)
  253. {
  254. return requests_pt(curl, req, url, data, NULL, 0, 1);
  255. }
  256. CURLcode requests_post_headers(CURL *curl, req_t *req, char *url, char *data,
  257. char **custom_hdrv, int custom_hdrc)
  258. {
  259. return requests_pt(curl, req, url, data, custom_hdrv, custom_hdrc, 0);
  260. }
  261. CURLcode requests_put_headers(CURL *curl, req_t *req, char *url, char *data,
  262. char **custom_hdrv, int custom_hdrc)
  263. {
  264. return requests_pt(curl, req, url, data, custom_hdrv, custom_hdrc, 1);
  265. }
  266. /*
  267. * requests_pt - Performs POST or PUT request using supplied data and populates
  268. * req struct text member with request response, code with response code, and
  269. * size with size of response. To submit no data, use NULL for data, and 0 for
  270. * data_size.
  271. *
  272. * Returns CURLcode provided from curl_easy_perform. CURLE_OK is returned on
  273. * success. -1 returned if there are issues with libcurl's internal linked list
  274. * append.
  275. *
  276. * Typically this function isn't used directly, use requests_post() or
  277. * requests_put() instead.
  278. *
  279. * @curl: libcurl handle
  280. * @req: request struct
  281. * @url: url to send request to
  282. * @data: url encoded data to send in request body
  283. * @custom_hdrv: char* array of custom headers
  284. * @custom_hdrc: length of `custom_hdrv`
  285. * @put_flag: if not zero, sends PUT request, otherwise uses POST
  286. */
  287. static CURLcode requests_pt(CURL *curl, req_t *req, char *url, char *data,
  288. char **custom_hdrv, int custom_hdrc, int put_flag)
  289. {
  290. CURLcode rc;
  291. struct curl_slist *slist = NULL;
  292. req->url = url;
  293. /* body data */
  294. if (data != NULL) {
  295. curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data);
  296. } else {
  297. /* content length header defaults to -1, which causes request to fail
  298. sometimes, so we need to manually set it to 0 */
  299. char *cl_header = "Content-Length: 0";
  300. slist = curl_slist_append(slist, cl_header);
  301. if (slist == NULL)
  302. return -1;
  303. if (custom_hdrv == NULL)
  304. curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist);
  305. hdrv_append(&req->req_hdrv, &req->req_hdrc, cl_header);
  306. }
  307. /* headers */
  308. if (custom_hdrv != NULL) {
  309. rc = process_custom_headers(&slist, req, custom_hdrv, custom_hdrc);
  310. if (rc != CURLE_OK)
  311. return rc;
  312. curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist);
  313. }
  314. common_opt(curl, req);
  315. if (put_flag)
  316. /* use custom request instead of dedicated PUT, because dedicated
  317. PUT doesn't work with arbitrary request body data */
  318. curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PUT");
  319. else
  320. curl_easy_setopt(curl, CURLOPT_POST, 1);
  321. char *ua = user_agent();
  322. curl_easy_setopt(curl, CURLOPT_USERAGENT, ua);
  323. rc = curl_easy_perform(curl);
  324. if (rc != CURLE_OK)
  325. return rc;
  326. long code;
  327. curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code);
  328. req->code = code;
  329. req->ok = check_ok(code);
  330. if (slist != NULL)
  331. curl_slist_free_all(slist);
  332. free(ua);
  333. curl_easy_cleanup(curl);
  334. return rc;
  335. }
  336. // Extension for patch requests
  337. CURLcode requests_patch(CURL *curl, req_t *req, char *url, char *data,
  338. char **custom_hdrv, int custom_hdrc, int is_json)
  339. {
  340. CURLcode rc;
  341. struct curl_slist *slist = NULL;
  342. req->url = url;
  343. /* body data */
  344. if (data != NULL) {
  345. curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data);
  346. } else {
  347. /* content length header defaults to -1, which causes request to fail
  348. sometimes, so we need to manually set it to 0 */
  349. char *cl_header = "Content-Length: 0";
  350. slist = curl_slist_append(slist, cl_header);
  351. if (slist == NULL)
  352. return -1;
  353. if (custom_hdrv == NULL)
  354. curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist);
  355. hdrv_append(&req->req_hdrv, &req->req_hdrc, cl_header);
  356. }
  357. /* headers */
  358. if (custom_hdrv != NULL) {
  359. rc = process_custom_headers(&slist, req, custom_hdrv, custom_hdrc);
  360. if (rc != CURLE_OK)
  361. return rc;
  362. curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist);
  363. }
  364. if (is_json) {
  365. curl_slist_append(slist, "Content-Type: application/json");
  366. }
  367. common_opt(curl, req);
  368. curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PATCH");
  369. char *ua = user_agent();
  370. curl_easy_setopt(curl, CURLOPT_USERAGENT, ua);
  371. rc = curl_easy_perform(curl);
  372. if (rc != CURLE_OK)
  373. return rc;
  374. long code;
  375. curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code);
  376. req->code = code;
  377. req->ok = check_ok(code);
  378. if (slist != NULL)
  379. curl_slist_free_all(slist);
  380. free(ua);
  381. curl_easy_cleanup(curl);
  382. return rc;
  383. }
  384. CURLcode requests_delete(CURL *curl, req_t *req, char *url, char *data,
  385. char **custom_hdrv, int custom_hdrc)
  386. {
  387. CURLcode rc;
  388. struct curl_slist *slist = NULL;
  389. req->url = url;
  390. /* body data */
  391. if (data != NULL) {
  392. curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data);
  393. } else {
  394. /* content length header defaults to -1, which causes request to fail
  395. sometimes, so we need to manually set it to 0 */
  396. char *cl_header = "Content-Length: 0";
  397. slist = curl_slist_append(slist, cl_header);
  398. if (slist == NULL)
  399. return -1;
  400. if (custom_hdrv == NULL)
  401. curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist);
  402. hdrv_append(&req->req_hdrv, &req->req_hdrc, cl_header);
  403. }
  404. /* headers */
  405. if (custom_hdrv != NULL) {
  406. rc = process_custom_headers(&slist, req, custom_hdrv, custom_hdrc);
  407. if (rc != CURLE_OK)
  408. return rc;
  409. curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist);
  410. }
  411. common_opt(curl, req);
  412. curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE");
  413. char *ua = user_agent();
  414. curl_easy_setopt(curl, CURLOPT_USERAGENT, ua);
  415. rc = curl_easy_perform(curl);
  416. if (rc != CURLE_OK)
  417. return rc;
  418. long code;
  419. curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code);
  420. req->code = code;
  421. req->ok = check_ok(code);
  422. if (slist != NULL)
  423. curl_slist_free_all(slist);
  424. free(ua);
  425. curl_easy_cleanup(curl);
  426. return rc;
  427. }
  428. /*
  429. * process_custom_headers - Adds custom headers to request and populates the
  430. * req_headerv and req_hdrc fields of the request struct using the supplied
  431. * custom headers.
  432. *
  433. * Returns CURLE_OK on success, CURLE_OUT_OF_MEMORY if realloc failed to
  434. * increase size of req_hdrv, or -1 if libcurl's linked list append fails.
  435. *
  436. * @slist: internal libcurl slist (string linked list)
  437. * @req: request struct
  438. * @custom_hdrv: char* array of custom headers
  439. * @custom_hdrc: length of `custom_hdrv`
  440. */
  441. static CURLcode process_custom_headers(struct curl_slist **slist, req_t *req,
  442. char **custom_hdrv, int custom_hdrc)
  443. {
  444. for (int i = 0; i < custom_hdrc; i++) {
  445. /* add header to request */
  446. *slist = curl_slist_append(*slist, custom_hdrv[i]);
  447. if (*slist == NULL)
  448. return -1;
  449. if (hdrv_append(&req->req_hdrv, &req->req_hdrc, custom_hdrv[i]))
  450. return CURLE_OUT_OF_MEMORY;
  451. }
  452. return CURLE_OK;
  453. }
  454. /*
  455. * hdrv_append - Appends to an arbitrary char* array and increments the given
  456. * array length.
  457. *
  458. * Returns 0 on success and -1 on memory error.
  459. *
  460. * @hdrv: pointer to the char* array
  461. * @hdrc: length of `hdrv' (NOTE: this value gets updated)
  462. * @new: char* to append
  463. */
  464. static int hdrv_append(char ***hdrv, int *hdrc, char *new)
  465. {
  466. /* current array size in bytes */
  467. size_t current_size = *hdrc * sizeof(char*);
  468. char *newdup = strndup(new, strlen(new));
  469. if (newdup == NULL)
  470. return -1;
  471. *hdrv = realloc(*hdrv, current_size + sizeof(char*));
  472. if (*hdrv == NULL)
  473. return -1;
  474. (*hdrc)++;
  475. (*hdrv)[*hdrc - 1] = newdup;
  476. return 0;
  477. }
  478. /*
  479. * common_opt - Sets common libcurl options.
  480. *
  481. * @curl: libcurl handle
  482. * @req: request struct
  483. */
  484. static void common_opt(CURL *curl, req_t *req)
  485. {
  486. curl_easy_setopt(curl, CURLOPT_URL, req->url);
  487. curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, resp_callback);
  488. curl_easy_setopt(curl, CURLOPT_WRITEDATA, req);
  489. curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, header_callback);
  490. curl_easy_setopt(curl, CURLOPT_HEADERDATA, req);
  491. }
  492. /*
  493. * user_agent - Creates custom user agent.
  494. *
  495. * Returns a char* containing the user agent, or NULL on failure.
  496. */
  497. static char *user_agent(void)
  498. {
  499. struct utsname name;
  500. uname(&name);
  501. char *kernel = name.sysname;
  502. char *version = name.release;
  503. char *ua;
  504. asprintf(&ua, "librequests/%s %s/%s", __LIBREQ_VERS__, kernel, version);
  505. return ua;
  506. }
  507. /*
  508. * check_ok - Utility function for setting "ok" struct field. Response codes
  509. * of 400+ are considered "not ok".
  510. *
  511. * @req: request struct
  512. */
  513. static int check_ok(long code)
  514. {
  515. if (code >= 400 || code == 0)
  516. return 0;
  517. else
  518. return 1;
  519. }