A tool for adding anime to your anidb list.
Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

813 рядки
21KB

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