A tool for adding anime to your anidb list.
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.

843 lines
22KB

  1. #include <stdbool.h>
  2. #include <string.h>
  3. #include <assert.h>
  4. #include <printf.h>
  5. #include <time.h>
  6. #include <pthread.h>
  7. #include <errno.h>
  8. #include <md5.h>
  9. #include <aes.h>
  10. #include "api.h"
  11. #include "net.h"
  12. #include "uio.h"
  13. #include "config.h"
  14. #include "ed2k.h"
  15. #include "util.h"
  16. #include "globals.h"
  17. /* Needed, bcuz of custom %B format */
  18. #pragma GCC diagnostic push
  19. #pragma GCC diagnostic ignored "-Wformat"
  20. #pragma GCC diagnostic ignored "-Wformat-extra-args"
  21. #ifdef CLOCK_MONOTONIC_COARSE
  22. #define API_CLOCK CLOCK_MONOTONIC_COARSE
  23. #elif defined(CLOCK_MONOTONIC)
  24. #warn "No coarse monotonic clock"
  25. #define API_CLOCK CLOCK_MONOTONIC
  26. #else
  27. #error "No monotonic clock"
  28. #endif
  29. #define MS_TO_TIMESPEC(ts, ms) { \
  30. ts->tv_sec = ms / 1000; \
  31. ts->tv_nsec = (ms % 1000) * 1000000; \
  32. }
  33. #define MS_TO_TIMESPEC_L(ts, ms) { \
  34. ts.tv_sec = ms / 1000; \
  35. ts.tv_nsec = (ms % 1000) * 1000000; \
  36. }
  37. static enum error api_cmd_logout(struct api_result *res);
  38. static enum error api_cmd_auth(const char *uname, const char *pass,
  39. struct api_result *res);
  40. static bool api_authed = false;
  41. static char api_session[API_SMAXSIZE] = {0}; /* No escaping is needed */
  42. static uint8_t e_key[16] = {0};
  43. static bool api_encryption = false;
  44. static pthread_t api_ka_thread = 0;
  45. static pthread_mutex_t api_work_mx;
  46. static bool api_ka_now = false; /* Are we doing keepalive now? */
  47. static struct timespec api_last_packet = {0}; /* Last packet time */
  48. static int32_t api_packet_count = 0; /* Only increment */
  49. //static int32_t api_fast_packet_count = 0; /* Increment or decrement */
  50. static int api_escaped_string(FILE *io, const struct printf_info *info,
  51. const void *const *args)
  52. {
  53. /* Ignore newline escapes for now */
  54. char *str = *(char**)args[0];
  55. char *and_pos = strchr(str, '&');
  56. size_t w_chars = 0;
  57. if (and_pos == NULL)
  58. return fprintf(io, "%s", str);
  59. while (and_pos) {
  60. w_chars += fprintf(io, "%.*s", (int)(and_pos - str), str);
  61. w_chars += fprintf(io, "&amp;");
  62. str = and_pos + 1;
  63. and_pos = strchr(str, '&');
  64. }
  65. if (*str)
  66. w_chars += fprintf(io, "%s", str);
  67. return w_chars;
  68. }
  69. static int api_escaped_sring_info(const struct printf_info *info, size_t n,
  70. int *argtypes, int *size)
  71. {
  72. if (n > 0) {
  73. argtypes[0] = PA_STRING;
  74. size[0] = sizeof(const char*);
  75. }
  76. return 1;
  77. }
  78. static enum error api_init_encrypt(const char *api_key, const char *uname)
  79. {
  80. char buffer[API_BUFSIZE];
  81. MD5Context md5_ctx;
  82. char *salt_start = buffer + 4 /* 209 [salt here] ... */, *salt_end;
  83. ssize_t r_len, salt_len;
  84. if (net_send(buffer, snprintf(buffer, sizeof(buffer),
  85. "ENCRYPT user=%s&type=1", uname)) == -1) {
  86. return ERR_API_COMMFAIL;
  87. }
  88. r_len = net_read(buffer, sizeof(buffer));
  89. if (strncmp(buffer, "209", 3) != 0) {
  90. uio_error("We expected 209 response, but got: %.*s",
  91. (int)r_len, buffer);
  92. return ERR_API_ENCRYPTFAIL;
  93. }
  94. salt_end = strchr(salt_start, ' ');
  95. if (!salt_end) {
  96. uio_error("Cannot find space after salt in response");
  97. return ERR_API_ENCRYPTFAIL;
  98. }
  99. salt_len = salt_end - salt_start;
  100. md5Init(&md5_ctx);
  101. md5Update(&md5_ctx, (uint8_t*)api_key, strlen(api_key));
  102. md5Update(&md5_ctx, (uint8_t*)salt_start, salt_len);
  103. md5Finalize(&md5_ctx);
  104. memcpy(e_key, md5_ctx.digest, sizeof(e_key));
  105. #if 1
  106. char *buffpos = buffer;
  107. for (int i = 0; i < 16; i++)
  108. buffpos += sprintf(buffpos, "%02x", e_key[i]);
  109. uio_debug("Encryption key is: '%s'", buffer);
  110. #endif
  111. api_encryption = true;
  112. return NOERR;
  113. }
  114. static size_t api_encrypt(char *buffer, size_t data_len)
  115. {
  116. struct AES_ctx actx;
  117. size_t rem_data_len = data_len, ret_len = data_len;
  118. char pad_value;
  119. AES_init_ctx(&actx, e_key);
  120. while (rem_data_len >= AES_BLOCKLEN) {
  121. AES_ECB_encrypt(&actx, (uint8_t*)buffer);
  122. buffer += AES_BLOCKLEN;
  123. rem_data_len -= AES_BLOCKLEN;
  124. }
  125. /* Possible BOF here? maybe? certanly. */
  126. pad_value = AES_BLOCKLEN - rem_data_len;
  127. ret_len += pad_value;
  128. memset(buffer + rem_data_len, pad_value, pad_value);
  129. AES_ECB_encrypt(&actx, (uint8_t*)buffer);
  130. assert(ret_len % AES_BLOCKLEN == 0);
  131. return ret_len;
  132. }
  133. static size_t api_decrypt(char *buffer, size_t data_len)
  134. {
  135. assert(data_len % AES_BLOCKLEN == 0);
  136. struct AES_ctx actx;
  137. size_t ret_len = data_len;
  138. char pad_value;
  139. AES_init_ctx(&actx, e_key);
  140. while (data_len) {
  141. AES_ECB_decrypt(&actx, (uint8_t*)buffer);
  142. buffer += AES_BLOCKLEN;
  143. data_len -= AES_BLOCKLEN;
  144. }
  145. pad_value = buffer[data_len - 1];
  146. ret_len -= pad_value;
  147. return ret_len;
  148. }
  149. static enum error api_auth(const char* uname, const char *passw)
  150. {
  151. struct api_result res;
  152. enum error err = NOERR;
  153. if (!api_encryption)
  154. uio_warning("Logging in without encryption!");
  155. if (api_cmd_auth(uname, passw, &res) != NOERR) {
  156. return ERR_API_AUTH_FAIL;
  157. }
  158. switch (res.code) {
  159. case 201:
  160. uio_warning("A new client version is available!");
  161. case 200:
  162. memcpy(api_session, res.auth.session_key, sizeof(api_session));
  163. api_authed = true;
  164. uio_debug("Succesfully logged in. Session key: '%s'", api_session);
  165. break;
  166. default:
  167. err = ERR_API_AUTH_FAIL;
  168. switch (res.code) {
  169. case 500:
  170. uio_error("Login failed. Please check your credentials again");
  171. break;
  172. case 503:
  173. uio_error("Client is outdated. You're probably out of luck here.");
  174. break;
  175. case 504:
  176. uio_error("Client is banned :( Reason: %s", res.auth.banned_reason);
  177. free(res.auth.banned_reason);
  178. break;
  179. case 505:
  180. uio_error("Illegal input or access denied");
  181. break;
  182. case 601:
  183. uio_error("AniDB out of service");
  184. break;
  185. default:
  186. uio_error("Unknown error: %hu", res.code);
  187. break;
  188. }
  189. }
  190. return err;
  191. }
  192. enum error api_logout()
  193. {
  194. struct api_result res;
  195. enum error err = NOERR;
  196. if (api_cmd_logout(&res) != NOERR) {
  197. return ERR_API_AUTH_FAIL;
  198. }
  199. switch (res.code) {
  200. case 203:
  201. uio_debug("Succesfully logged out");
  202. api_authed = false;
  203. break;
  204. case 403:
  205. uio_error("Cannot log out, because we aren't logged in");
  206. api_authed = false;
  207. break;
  208. default:
  209. err = ERR_API_LOGOUT;
  210. uio_error("Unknown error: %hu", res.code);
  211. break;
  212. }
  213. return err;
  214. }
  215. static void api_keepalive(struct timespec *out_next)
  216. {
  217. struct timespec ts = {0};
  218. uint64_t msdiff;
  219. clock_gettime(API_CLOCK, &ts);
  220. msdiff = util_timespec_diff(&api_last_packet, &ts);
  221. if (msdiff >= API_TIMEOUT) {
  222. struct api_result r;
  223. MS_TO_TIMESPEC(out_next, API_TIMEOUT);
  224. uio_debug("Sending uptime command for keep alive");
  225. // TODO what if another action is already in progress?
  226. api_cmd_uptime(&r);
  227. } else {
  228. uint64_t msnext = API_TIMEOUT - msdiff;
  229. uio_debug("Got keepalive request, but time is not up yet");
  230. MS_TO_TIMESPEC(out_next, msnext);
  231. }
  232. }
  233. void *api_keepalive_main(void *arg)
  234. {
  235. struct timespec ka_time;
  236. MS_TO_TIMESPEC_L(ka_time, API_TIMEOUT);
  237. uio_debug("Hi from keepalie thread");
  238. for (;;) {
  239. if (nanosleep(&ka_time, NULL) != 0) {
  240. int e = errno;
  241. uio_error("Nanosleep failed: %s", strerror(e));
  242. }
  243. /* Needed, because the thread could be canceled while in recv or send
  244. * and in that case, the mutex will remain locked
  245. * Could be replaced with a pthread_cleanup_push ? */
  246. pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
  247. pthread_mutex_lock(&api_work_mx);
  248. api_ka_now = true;
  249. uio_debug("G'moooooning! Is it time to keep our special connection alive?");
  250. api_keepalive(&ka_time);
  251. uio_debug("Next wakey-wakey in %ld seconds", ka_time.tv_sec);
  252. api_ka_now = false;
  253. pthread_mutex_unlock(&api_work_mx);
  254. pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
  255. }
  256. return NULL;
  257. }
  258. enum error api_clock_init()
  259. {
  260. struct timespec ts;
  261. memset(&api_last_packet, 0, sizeof(api_last_packet));
  262. api_packet_count = 0;
  263. if (clock_getres(API_CLOCK, &ts) != 0) {
  264. uio_error("Cannot get clock resolution: %s", strerror(errno));
  265. return ERR_API_CLOCK;
  266. }
  267. uio_debug("Clock resolution: %f ms",
  268. (ts.tv_sec * 1000) + (ts.tv_nsec / 1000000.0));
  269. return NOERR;
  270. }
  271. enum error api_init(bool auth)
  272. {
  273. enum error err = NOERR;
  274. const char **api_key, **uname, **passwd;
  275. err = api_clock_init();
  276. if (err != NOERR)
  277. return err;
  278. err = net_init();
  279. if (err != NOERR)
  280. return err;
  281. if (config_get("api-key", (void**)&api_key) == NOERR) {
  282. if (config_get("username", (void**)&uname) != NOERR) {
  283. uio_error("Api key is specified, but that also requires "
  284. "the username!");
  285. err = ERR_OPT_REQUIRED;
  286. goto fail;
  287. }
  288. err = api_init_encrypt(*api_key, *uname);
  289. if (err != NOERR) {
  290. uio_error("Cannot init api encryption");
  291. goto fail;
  292. }
  293. }
  294. /* Define an escaped string printf type */
  295. if (register_printf_specifier('B', api_escaped_string,
  296. api_escaped_sring_info) != 0) {
  297. uio_error("Failed to register escaped printf string function");
  298. err = ERR_API_PRINTFFUNC;
  299. goto fail;
  300. }
  301. if (auth) {
  302. if (config_get("username", (void**)&uname) != NOERR) {
  303. uio_error("Username is not specified, but it is required!");
  304. err = ERR_OPT_REQUIRED;
  305. goto fail;
  306. }
  307. if (config_get("password", (void**)&passwd) != NOERR) {
  308. uio_error("Password is not specified, but it is required!");
  309. err = ERR_OPT_REQUIRED;
  310. goto fail;
  311. }
  312. err = api_auth(*uname, *passwd);
  313. if (err != NOERR)
  314. goto fail;
  315. /* Only do keep alive if we have a session */
  316. if (pthread_mutex_init(&api_work_mx, NULL) != 0) {
  317. uio_error("Cannot create mutex");
  318. err = ERR_THRD;
  319. goto fail;
  320. }
  321. if (pthread_create(&api_ka_thread, NULL, api_keepalive_main, NULL) != 0) {
  322. uio_error("Cannot create api keepalive thread");
  323. err = ERR_THRD;
  324. goto fail;
  325. }
  326. }
  327. #if 0
  328. printf("Testings: %B\n", "oi&ha=hi&wooooowz&");
  329. printf("Testings: %B\n", "oi&ha=hi&wooooowz");
  330. printf("Testings: %B\n", "&oi&ha=hi&wooooowz");
  331. printf("Testings: %B\n", "oooooooooiiiiii");
  332. #endif
  333. return err;
  334. fail:
  335. api_free();
  336. return err;
  337. }
  338. void api_free()
  339. {
  340. if (api_authed) {
  341. if (pthread_cancel(api_ka_thread) != 0) {
  342. uio_error("Cannot cancel api keepalive thread");
  343. } else {
  344. int je = pthread_join(api_ka_thread, NULL);
  345. if (je != 0)
  346. uio_error("Cannot join api keepalive thread: %s",
  347. strerror(je));
  348. else
  349. uio_debug("Keepalive thread ended");
  350. if (pthread_mutex_destroy(&api_work_mx) != 0)
  351. uio_error("Cannot destroy api work mutex");
  352. }
  353. api_logout();
  354. memset(api_session, 0, sizeof(api_session));
  355. api_authed = false; /* duplicate */
  356. }
  357. if (api_encryption) {
  358. api_encryption = false;
  359. memset(e_key, 0, sizeof(e_key));
  360. }
  361. register_printf_specifier('B', NULL, NULL);
  362. net_free();
  363. }
  364. /*
  365. * We just sent a packet, so update the last packet time here
  366. */
  367. static void api_ratelimit_sent()
  368. {
  369. api_packet_count++;
  370. clock_gettime(API_CLOCK, &api_last_packet);
  371. }
  372. static void api_ratelimit()
  373. {
  374. struct timespec ts = {0};
  375. uint64_t msdiff, mswait;
  376. uint64_t msrate = api_packet_count >= API_LONGTERM_PACKETS ?
  377. API_SENDWAIT_LONG : API_SENDWAIT;
  378. /* First of all, the first N packets are unmetered */
  379. if (api_packet_count <= API_FREESEND) {
  380. uio_debug("This packet is for free! Yay :D (%d/%d)",
  381. api_packet_count, API_FREESEND);
  382. return;
  383. }
  384. clock_gettime(API_CLOCK, &ts);
  385. msdiff = util_timespec_diff(&api_last_packet, &ts);
  386. uio_debug("Time since last packet: %ld ms", msdiff);
  387. if (msdiff >= msrate)
  388. return; /* No ratelimiting is needed */
  389. /* Need ratelimit, so do it here for now */
  390. mswait = msrate - msdiff;
  391. uio_debug("Ratelimit is needed, sleeping for %ld ms", mswait);
  392. MS_TO_TIMESPEC_L(ts, mswait);
  393. if (nanosleep(&ts, NULL) == -1) {
  394. if (errno == EINTR)
  395. uio_error("Nanosleep got interrupted");
  396. else
  397. uio_error("Nanosleep failed");
  398. }
  399. }
  400. /*
  401. * Returns the written byte count
  402. * Or -1 on error, and -2 if errno was EINTR
  403. */
  404. static ssize_t api_send(char *buffer, size_t data_len, size_t buf_size)
  405. {
  406. ssize_t read_len;
  407. int en;
  408. api_ratelimit();
  409. uio_debug("{Api}: Sending: %.*s", (int)data_len, buffer);
  410. if (api_encryption)
  411. data_len = api_encrypt(buffer, data_len);
  412. en = net_send(buffer, data_len);
  413. if (en < 0)
  414. return en;
  415. read_len = net_read(buffer, buf_size);
  416. if (read_len < 0) {
  417. uio_error("!!! BAD PLACE EINTR !!! report pls");
  418. return en; /* This could lead so some problems if we also want to
  419. log out. If we hit this, the msg got sent, but we
  420. couldn't read the response. That means, in the
  421. logout call, this msg's data will be read
  422. Let's see if this ever comes up */
  423. }
  424. api_ratelimit_sent();
  425. if (api_encryption)
  426. read_len = api_decrypt(buffer, read_len);
  427. uio_debug("{Api}: Reading: %.*s", (int)read_len, buffer);
  428. return read_len;
  429. }
  430. long api_res_code(const char *buffer)
  431. {
  432. char *end;
  433. long res = strtol(buffer, &end, 10);
  434. if (res == 0 && buffer == end) {
  435. uio_error("No error codes in the response");
  436. return -1;
  437. }
  438. assert(*end == ' ');
  439. return res;
  440. }
  441. static bool api_get_fl(const char *buffer, int32_t index, const char *delim,
  442. char **const out_start, size_t *const out_len)
  443. {
  444. assert(index > 0);
  445. size_t len = strcspn(buffer, delim);
  446. while (--index > 0) {
  447. buffer += len + 1;
  448. len = strcspn(buffer, delim);
  449. }
  450. *out_start = (char*)buffer;
  451. *out_len = len;
  452. return true;
  453. }
  454. static bool api_get_line(const char *buffer, int32_t line_num,
  455. char **const out_line_start, size_t *const out_line_len)
  456. {
  457. return api_get_fl(buffer, line_num, "\n", out_line_start, out_line_len);
  458. }
  459. static bool api_get_field(const char *buffer, int32_t field_num,
  460. char **const out_field_start, size_t *const out_field_len)
  461. {
  462. return api_get_fl(buffer, field_num, " |\n", out_field_start, out_field_len);
  463. }
  464. #if 0
  465. static char *api_get_field_mod(char *buffer, int32_t field_num)
  466. {
  467. char *sptr = NULL;
  468. char *f_start;
  469. f_start = strtok_r(buffer, " ", &sptr);
  470. if (!f_start)
  471. return NULL;
  472. while (field_num --> 0) {
  473. f_start = strtok_r(NULL, " ", &sptr);
  474. if (!f_start)
  475. return NULL;
  476. }
  477. return f_start;
  478. }
  479. #endif
  480. enum error api_cmd_version(struct api_result *res)
  481. {
  482. char buffer[API_BUFSIZE] = "VERSION";
  483. size_t res_len = api_send(buffer, strlen(buffer), sizeof(buffer));
  484. long code;
  485. enum error err = NOERR;
  486. pthread_mutex_lock(&api_work_mx);
  487. if (res_len < 0) {
  488. if (res_len == -2 && should_exit)
  489. err = ERR_SHOULD_EXIT;
  490. else
  491. err = ERR_API_COMMFAIL;
  492. goto end;
  493. }
  494. code = api_res_code(buffer);
  495. if (code == -1) {
  496. err = ERR_API_RESP_INVALID;
  497. goto end;
  498. }
  499. if (code == 998) {
  500. char *ver_start;
  501. size_t ver_len;
  502. bool glr = api_get_line(buffer, 2, &ver_start, &ver_len);
  503. assert(glr);
  504. (void)glr;
  505. assert(ver_len < sizeof(res->version.version_str));
  506. memcpy(res->version.version_str, ver_start, ver_len);
  507. res->version.version_str[ver_len] = '\0';
  508. }
  509. res->code = (uint16_t)code;
  510. end:
  511. pthread_mutex_unlock(&api_work_mx);
  512. return err;
  513. }
  514. static enum error api_cmd_auth(const char *uname, const char *pass,
  515. struct api_result *res)
  516. {
  517. pthread_mutex_lock(&api_work_mx);
  518. char buffer[API_BUFSIZE];
  519. long code;
  520. size_t res_len = api_send(buffer, snprintf(buffer, sizeof(buffer),
  521. "AUTH user=%s&pass=%B&protover=" API_VERSION "&client=caniadd&"
  522. "clientver=" PROG_VERSION "&enc=UTF-8", uname, pass),
  523. sizeof(buffer));
  524. enum error err = NOERR;
  525. if (res_len < 0) {
  526. if (res_len == -2 && should_exit)
  527. err = ERR_SHOULD_EXIT;
  528. else
  529. err = ERR_API_COMMFAIL;
  530. goto end;
  531. }
  532. code = api_res_code(buffer);
  533. if (code == -1) {
  534. err = ERR_API_RESP_INVALID;
  535. goto end;
  536. }
  537. if (code == 200 || code == 201) {
  538. char *sess;
  539. size_t sess_len;
  540. bool gfr = api_get_field(buffer, 2, &sess, &sess_len);
  541. assert(gfr);
  542. (void)gfr;
  543. assert(sess_len < sizeof(res->auth.session_key));
  544. memcpy(res->auth.session_key, sess, sess_len);
  545. res->auth.session_key[sess_len] = '\0';
  546. } else if (code == 504) {
  547. char *reason;
  548. size_t reason_len;
  549. bool gfr = api_get_field(buffer, 5, &reason, &reason_len);
  550. assert(gfr);
  551. (void)gfr;
  552. res->auth.banned_reason = strndup(reason, reason_len);
  553. }
  554. res->code = (uint16_t)code;
  555. end:
  556. pthread_mutex_unlock(&api_work_mx);
  557. return err;
  558. }
  559. static enum error api_cmd_logout(struct api_result *res)
  560. {
  561. pthread_mutex_lock(&api_work_mx);
  562. char buffer[API_BUFSIZE];
  563. size_t res_len = api_send(buffer, snprintf(buffer, sizeof(buffer),
  564. "LOGOUT s=%s", api_session), sizeof(buffer));
  565. long code;
  566. enum error err = NOERR;
  567. if (res_len < 0) {
  568. if (res_len == -2 && should_exit)
  569. err = ERR_SHOULD_EXIT;
  570. else
  571. err = ERR_API_COMMFAIL;
  572. goto end;
  573. }
  574. code = api_res_code(buffer);
  575. if (code == -1) {
  576. err = ERR_API_RESP_INVALID;
  577. goto end;
  578. }
  579. res->code = (uint16_t)code;
  580. end:
  581. pthread_mutex_unlock(&api_work_mx);
  582. return err;
  583. }
  584. enum error api_cmd_uptime(struct api_result *res)
  585. {
  586. /* If mutex is not already locked from the keepalive thread */
  587. /* Or we could use a recursive mutex? */
  588. if (!api_ka_now)
  589. pthread_mutex_lock(&api_work_mx);
  590. char buffer[API_BUFSIZE];
  591. size_t res_len = api_send(buffer, snprintf(buffer, sizeof(buffer),
  592. "UPTIME s=%s", api_session), sizeof(buffer));
  593. long code;
  594. enum error err = NOERR;
  595. if (res_len < 0) {
  596. if (res_len == -2 && should_exit)
  597. err = ERR_SHOULD_EXIT;
  598. else
  599. err = ERR_API_COMMFAIL;
  600. goto end;
  601. }
  602. code = api_res_code(buffer);
  603. if (code == -1) {
  604. err = ERR_API_RESP_INVALID;
  605. goto end;
  606. }
  607. if (code == 208) {
  608. char *ls;
  609. size_t ll;
  610. bool glf = api_get_line(buffer, 2, &ls, &ll);
  611. assert(glf);
  612. (void)glf;
  613. res->uptime.ms = strtol(ls, NULL, 10);
  614. }
  615. res->code = (uint16_t)code;
  616. end:
  617. if (!api_ka_now)
  618. pthread_mutex_unlock(&api_work_mx);
  619. return err;
  620. }
  621. enum error api_cmd_mylistadd(int64_t size, const uint8_t *hash,
  622. enum mylist_state ml_state, bool watched, struct api_result *res)
  623. {
  624. char buffer[API_BUFSIZE];
  625. char hash_str[ED2K_HASH_SIZE * 2 + 1];
  626. size_t res_len;
  627. enum error err = NOERR;
  628. long code;
  629. pthread_mutex_lock(&api_work_mx);
  630. util_byte2hex(hash, ED2K_HASH_SIZE, false, hash_str);
  631. /* Wiki says file size is 4 bytes, but no way that's true lol */
  632. res_len = api_send(buffer, snprintf(buffer, sizeof(buffer),
  633. "MYLISTADD s=%s&size=%ld&ed2k=%s&state=%hu&viewed=%d",
  634. api_session, size, hash_str, ml_state, watched),
  635. sizeof(buffer));
  636. if (res_len < 0) {
  637. if (res_len == -2 && should_exit)
  638. err = ERR_SHOULD_EXIT;
  639. else
  640. err = ERR_API_COMMFAIL;
  641. goto end;
  642. }
  643. code = api_res_code(buffer);
  644. if (code == -1) {
  645. err = ERR_API_RESP_INVALID;
  646. goto end;
  647. }
  648. if (code == 210) {
  649. char *ls, id_str[12];
  650. size_t ll;
  651. bool glr = api_get_line(buffer, 2, &ls, &ll);
  652. assert(glr);
  653. (void)glr;
  654. assert(sizeof(id_str) > ll);
  655. memcpy(id_str, ls, ll);
  656. id_str[ll] = '\0';
  657. res->mylistadd.new_id = strtoll(id_str, NULL, 10);
  658. /* Wiki says these id's are 4 bytes, which is untrue...
  659. * that page may be a little out of date (or they just
  660. * expect us to use common sense lmao */
  661. } else if (code == 310) {
  662. /* {int4 lid}|{int4 fid}|{int4 eid}|{int4 aid}|{int4 gid}|
  663. * {int4 date}|{int2 state}|{int4 viewdate}|{str storage}|
  664. * {str source}|{str other}|{int2 filestate} */
  665. char *ls;
  666. size_t ll;
  667. struct api_mylistadd_result *mr = &res->mylistadd;
  668. bool glr = api_get_line(buffer, 2, &ls, &ll);
  669. assert(glr);
  670. assert(ll < API_BUFSIZE - 1);
  671. (void)glr;
  672. ls[ll] = '\0';
  673. void *fptrs[] = {
  674. &mr->lid, &mr->fid, &mr->eid, &mr->aid, &mr->gid, &mr->date,
  675. &mr->state, &mr->viewdate, &mr->storage, &mr->source,
  676. &mr->other, &mr->filestate,
  677. };
  678. for (int idx = 1; idx <= 12; idx++) {
  679. char *fs, *endptr;
  680. size_t fl;
  681. bool pr;
  682. uint64_t val;
  683. size_t cpy_size = sizeof(mr->lid);
  684. if (idx == 7)
  685. cpy_size = sizeof(mr->state);
  686. if (idx == 12)
  687. cpy_size = sizeof(mr->filestate);
  688. pr = api_get_field(ls, idx, &fs, &fl);
  689. assert(pr);
  690. (void)pr;
  691. if (idx == 9 || idx == 10 || idx == 11) { /* string fields */
  692. if (fl == 0)
  693. *(char**)fptrs[idx-1] = NULL;
  694. else
  695. *(char**)fptrs[idx-1] = strndup(fs, fl);
  696. continue;
  697. }
  698. val = strtoull(fs, &endptr, 10);
  699. assert(!(val == 0 && fs == endptr));
  700. memcpy(fptrs[idx-1], &val, cpy_size);
  701. }
  702. }
  703. res->code = (uint16_t)code;
  704. end:
  705. pthread_mutex_unlock(&api_work_mx);
  706. return err;
  707. }
  708. #pragma GCC diagnostic pop