rc_api_common.c (36364B)
1 #include "rc_api_common.h" 2 #include "rc_api_request.h" 3 #include "rc_api_runtime.h" 4 5 #include "../rc_compat.h" 6 7 #include <ctype.h> 8 #include <stdio.h> 9 #include <stdlib.h> 10 #include <string.h> 11 12 #define RETROACHIEVEMENTS_HOST "https://retroachievements.org" 13 #define RETROACHIEVEMENTS_IMAGE_HOST "https://media.retroachievements.org" 14 #define RETROACHIEVEMENTS_HOST_NONSSL "http://retroachievements.org" 15 #define RETROACHIEVEMENTS_IMAGE_HOST_NONSSL "http://media.retroachievements.org" 16 static char* g_host = NULL; 17 static char* g_imagehost = NULL; 18 19 /* --- rc_json --- */ 20 21 static int rc_json_parse_object(rc_json_iterator_t* iterator, rc_json_field_t* fields, size_t field_count, uint32_t* fields_seen); 22 static int rc_json_parse_array(rc_json_iterator_t* iterator, rc_json_field_t* field); 23 24 static int rc_json_match_char(rc_json_iterator_t* iterator, char c) 25 { 26 if (iterator->json < iterator->end && *iterator->json == c) { 27 ++iterator->json; 28 return 1; 29 } 30 31 return 0; 32 } 33 34 static void rc_json_skip_whitespace(rc_json_iterator_t* iterator) 35 { 36 while (iterator->json < iterator->end && isspace((unsigned char)*iterator->json)) 37 ++iterator->json; 38 } 39 40 static int rc_json_find_substring(rc_json_iterator_t* iterator, const char* substring) 41 { 42 const char first = *substring; 43 const size_t substring_len = strlen(substring); 44 const char* end = iterator->end - substring_len; 45 46 while (iterator->json <= end) { 47 if (*iterator->json == first) { 48 if (memcmp(iterator->json, substring, substring_len) == 0) 49 return 1; 50 } 51 52 ++iterator->json; 53 } 54 55 return 0; 56 } 57 58 static int rc_json_find_closing_quote(rc_json_iterator_t* iterator) 59 { 60 while (iterator->json < iterator->end) { 61 if (*iterator->json == '"') 62 return 1; 63 64 if (*iterator->json == '\\') { 65 ++iterator->json; 66 if (iterator->json == iterator->end) 67 return 0; 68 } 69 70 if (*iterator->json == '\0') 71 return 0; 72 73 ++iterator->json; 74 } 75 76 return 0; 77 } 78 79 static int rc_json_parse_field(rc_json_iterator_t* iterator, rc_json_field_t* field) { 80 int result; 81 82 if (iterator->json >= iterator->end) 83 return RC_INVALID_JSON; 84 85 field->value_start = iterator->json; 86 87 switch (*iterator->json) 88 { 89 case '"': /* quoted string */ 90 ++iterator->json; 91 if (!rc_json_find_closing_quote(iterator)) 92 return RC_INVALID_JSON; 93 ++iterator->json; 94 break; 95 96 case '-': 97 case '+': /* signed number */ 98 ++iterator->json; 99 /* fallthrough to number */ 100 case '0': case '1': case '2': case '3': case '4': 101 case '5': case '6': case '7': case '8': case '9': /* number */ 102 while (iterator->json < iterator->end && *iterator->json >= '0' && *iterator->json <= '9') 103 ++iterator->json; 104 105 if (rc_json_match_char(iterator, '.')) { 106 while (iterator->json < iterator->end && *iterator->json >= '0' && *iterator->json <= '9') 107 ++iterator->json; 108 } 109 break; 110 111 case '[': /* array */ 112 result = rc_json_parse_array(iterator, field); 113 if (result != RC_OK) 114 return result; 115 116 break; 117 118 case '{': /* object */ 119 result = rc_json_parse_object(iterator, NULL, 0, &field->array_size); 120 if (result != RC_OK) 121 return result; 122 123 break; 124 125 default: /* non-quoted text [true,false,null] */ 126 if (!isalpha((unsigned char)*iterator->json)) 127 return RC_INVALID_JSON; 128 129 while (iterator->json < iterator->end && isalnum((unsigned char)*iterator->json)) 130 ++iterator->json; 131 break; 132 } 133 134 field->value_end = iterator->json; 135 return RC_OK; 136 } 137 138 static int rc_json_parse_array(rc_json_iterator_t* iterator, rc_json_field_t* field) { 139 rc_json_field_t unused_field; 140 int result; 141 142 if (!rc_json_match_char(iterator, '[')) 143 return RC_INVALID_JSON; 144 145 field->array_size = 0; 146 147 if (rc_json_match_char(iterator, ']')) /* empty array */ 148 return RC_OK; 149 150 do 151 { 152 rc_json_skip_whitespace(iterator); 153 154 result = rc_json_parse_field(iterator, &unused_field); 155 if (result != RC_OK) 156 return result; 157 158 ++field->array_size; 159 160 rc_json_skip_whitespace(iterator); 161 } while (rc_json_match_char(iterator, ',')); 162 163 if (!rc_json_match_char(iterator, ']')) 164 return RC_INVALID_JSON; 165 166 return RC_OK; 167 } 168 169 static int rc_json_get_next_field(rc_json_iterator_t* iterator, rc_json_field_t* field) { 170 rc_json_skip_whitespace(iterator); 171 172 if (!rc_json_match_char(iterator, '"')) 173 return RC_INVALID_JSON; 174 175 field->name = iterator->json; 176 while (iterator->json < iterator->end && *iterator->json != '"') { 177 if (!*iterator->json) 178 return RC_INVALID_JSON; 179 ++iterator->json; 180 } 181 182 if (iterator->json == iterator->end) 183 return RC_INVALID_JSON; 184 185 field->name_len = iterator->json - field->name; 186 ++iterator->json; 187 188 rc_json_skip_whitespace(iterator); 189 190 if (!rc_json_match_char(iterator, ':')) 191 return RC_INVALID_JSON; 192 193 rc_json_skip_whitespace(iterator); 194 195 if (rc_json_parse_field(iterator, field) < 0) 196 return RC_INVALID_JSON; 197 198 rc_json_skip_whitespace(iterator); 199 200 return RC_OK; 201 } 202 203 static int rc_json_parse_object(rc_json_iterator_t* iterator, rc_json_field_t* fields, size_t field_count, uint32_t* fields_seen) { 204 size_t i; 205 uint32_t num_fields = 0; 206 rc_json_field_t field; 207 int result; 208 209 if (fields_seen) 210 *fields_seen = 0; 211 212 for (i = 0; i < field_count; ++i) 213 fields[i].value_start = fields[i].value_end = NULL; 214 215 if (!rc_json_match_char(iterator, '{')) 216 return RC_INVALID_JSON; 217 218 if (rc_json_match_char(iterator, '}')) /* empty object */ 219 return RC_OK; 220 221 do 222 { 223 result = rc_json_get_next_field(iterator, &field); 224 if (result != RC_OK) 225 return result; 226 227 for (i = 0; i < field_count; ++i) { 228 if (!fields[i].value_start && fields[i].name_len == field.name_len && 229 memcmp(fields[i].name, field.name, field.name_len) == 0) { 230 fields[i].value_start = field.value_start; 231 fields[i].value_end = field.value_end; 232 fields[i].array_size = field.array_size; 233 break; 234 } 235 } 236 237 ++num_fields; 238 239 } while (rc_json_match_char(iterator, ',')); 240 241 if (!rc_json_match_char(iterator, '}')) 242 return RC_INVALID_JSON; 243 244 if (fields_seen) 245 *fields_seen = num_fields; 246 247 return RC_OK; 248 } 249 250 int rc_json_get_next_object_field(rc_json_iterator_t* iterator, rc_json_field_t* field) { 251 if (!rc_json_match_char(iterator, ',') && !rc_json_match_char(iterator, '{')) 252 return 0; 253 254 return (rc_json_get_next_field(iterator, field) == RC_OK); 255 } 256 257 int rc_json_get_object_string_length(const char* json) { 258 rc_json_iterator_t iterator; 259 memset(&iterator, 0, sizeof(iterator)); 260 iterator.json = json; 261 iterator.end = json + (1024 * 1024 * 1024); /* arbitrary 1GB limit on JSON response */ 262 263 rc_json_parse_object(&iterator, NULL, 0, NULL); 264 265 if (iterator.json == json) /* not JSON */ 266 return (int)strlen(json); 267 268 return (int)(iterator.json - json); 269 } 270 271 static int rc_json_extract_html_error(rc_api_response_t* response, const rc_api_server_response_t* server_response) { 272 rc_json_iterator_t iterator; 273 memset(&iterator, 0, sizeof(iterator)); 274 iterator.json = server_response->body; 275 iterator.end = server_response->body + server_response->body_length; 276 277 /* if the title contains an HTTP status code(i.e "404 Not Found"), return the title */ 278 if (rc_json_find_substring(&iterator, "<title>")) { 279 const char* title_start = iterator.json + 7; 280 if (isdigit((int)*title_start) && rc_json_find_substring(&iterator, "</title>")) { 281 response->error_message = rc_buffer_strncpy(&response->buffer, title_start, iterator.json - title_start); 282 response->succeeded = 0; 283 return RC_INVALID_JSON; 284 } 285 } 286 287 /* title not found, or did not start with an error code, return the first line of the response */ 288 iterator.json = server_response->body; 289 290 while (iterator.json < iterator.end && *iterator.json != '\n' && 291 iterator.json - server_response->body < 200) { 292 ++iterator.json; 293 } 294 295 if (iterator.json > server_response->body && iterator.json[-1] == '\r') 296 --iterator.json; 297 298 if (iterator.json > server_response->body) 299 response->error_message = rc_buffer_strncpy(&response->buffer, server_response->body, iterator.json - server_response->body); 300 301 response->succeeded = 0; 302 return RC_INVALID_JSON; 303 } 304 305 static int rc_json_convert_error_code(const char* server_error_code) 306 { 307 switch (server_error_code[0]) { 308 case 'a': 309 if (strcmp(server_error_code, "access_denied") == 0) 310 return RC_ACCESS_DENIED; 311 break; 312 313 case 'e': 314 if (strcmp(server_error_code, "expired_token") == 0) 315 return RC_EXPIRED_TOKEN; 316 break; 317 318 case 'i': 319 if (strcmp(server_error_code, "invalid_credentials") == 0) 320 return RC_INVALID_CREDENTIALS; 321 break; 322 323 default: 324 break; 325 } 326 327 return RC_API_FAILURE; 328 } 329 330 int rc_json_parse_server_response(rc_api_response_t* response, const rc_api_server_response_t* server_response, rc_json_field_t* fields, size_t field_count) { 331 int result; 332 333 #ifndef NDEBUG 334 if (field_count < 2) 335 return RC_INVALID_STATE; 336 if (strcmp(fields[0].name, "Success") != 0) 337 return RC_INVALID_STATE; 338 if (strcmp(fields[1].name, "Error") != 0) 339 return RC_INVALID_STATE; 340 #endif 341 342 response->error_message = NULL; 343 344 if (!server_response) { 345 response->succeeded = 0; 346 return RC_NO_RESPONSE; 347 } 348 349 if (server_response->http_status_code == RC_API_SERVER_RESPONSE_CLIENT_ERROR || 350 server_response->http_status_code == RC_API_SERVER_RESPONSE_RETRYABLE_CLIENT_ERROR) { 351 /* client provided error message is passed as the response body */ 352 response->error_message = server_response->body; 353 response->succeeded = 0; 354 return RC_NO_RESPONSE; 355 } 356 357 if (!server_response->body || !*server_response->body) { 358 /* expect valid HTTP status codes to have bodies that we can extract the message from, 359 * but provide some default messages in case they don't. */ 360 switch (server_response->http_status_code) { 361 case 504: /* 504 Gateway Timeout */ 362 case 522: /* 522 Connection Timed Out */ 363 case 524: /* 524 A Timeout Occurred */ 364 response->error_message = "Request has timed out."; 365 break; 366 367 case 521: /* 521 Web Server is Down */ 368 case 523: /* 523 Origin is Unreachable */ 369 response->error_message = "Could not connect to server."; 370 break; 371 372 default: 373 break; 374 } 375 376 response->succeeded = 0; 377 return RC_NO_RESPONSE; 378 } 379 380 if (*server_response->body != '{') { 381 result = rc_json_extract_html_error(response, server_response); 382 } 383 else { 384 rc_json_iterator_t iterator; 385 memset(&iterator, 0, sizeof(iterator)); 386 iterator.json = server_response->body; 387 iterator.end = server_response->body + server_response->body_length; 388 result = rc_json_parse_object(&iterator, fields, field_count, NULL); 389 390 rc_json_get_optional_string(&response->error_message, response, &fields[1], "Error", NULL); 391 rc_json_get_optional_bool(&response->succeeded, &fields[0], "Success", 1); 392 393 /* Code will be the third field in the fields array, but may not always be present */ 394 if (field_count > 2 && strcmp(fields[2].name, "Code") == 0) { 395 rc_json_get_optional_string(&response->error_code, response, &fields[2], "Code", NULL); 396 if (response->error_code != NULL) 397 result = rc_json_convert_error_code(response->error_code); 398 } 399 } 400 401 return result; 402 } 403 404 static int rc_json_missing_field(rc_api_response_t* response, const rc_json_field_t* field) { 405 const char* not_found = " not found in response"; 406 const size_t not_found_len = strlen(not_found); 407 const size_t field_len = strlen(field->name); 408 409 uint8_t* write = rc_buffer_reserve(&response->buffer, field_len + not_found_len + 1); 410 if (write) { 411 response->error_message = (char*)write; 412 memcpy(write, field->name, field_len); 413 write += field_len; 414 memcpy(write, not_found, not_found_len + 1); 415 write += not_found_len + 1; 416 rc_buffer_consume(&response->buffer, (uint8_t*)response->error_message, write); 417 } 418 419 response->succeeded = 0; 420 return 0; 421 } 422 423 int rc_json_get_required_object(rc_json_field_t* fields, size_t field_count, rc_api_response_t* response, rc_json_field_t* field, const char* field_name) { 424 rc_json_iterator_t iterator; 425 426 #ifndef NDEBUG 427 if (strcmp(field->name, field_name) != 0) 428 return 0; 429 #else 430 (void)field_name; 431 #endif 432 433 if (!field->value_start) 434 return rc_json_missing_field(response, field); 435 436 memset(&iterator, 0, sizeof(iterator)); 437 iterator.json = field->value_start; 438 iterator.end = field->value_end; 439 return (rc_json_parse_object(&iterator, fields, field_count, &field->array_size) == RC_OK); 440 } 441 442 static int rc_json_get_array_entry_value(rc_json_field_t* field, rc_json_iterator_t* iterator) { 443 rc_json_skip_whitespace(iterator); 444 445 if (iterator->json >= iterator->end) 446 return 0; 447 448 if (rc_json_parse_field(iterator, field) != RC_OK) 449 return 0; 450 451 rc_json_skip_whitespace(iterator); 452 453 if (!rc_json_match_char(iterator, ',')) 454 rc_json_match_char(iterator, ']'); 455 456 return 1; 457 } 458 459 int rc_json_get_required_unum_array(uint32_t** entries, uint32_t* num_entries, rc_api_response_t* response, const rc_json_field_t* field, const char* field_name) { 460 rc_json_iterator_t iterator; 461 rc_json_field_t array; 462 rc_json_field_t value; 463 uint32_t* entry; 464 465 memset(&array, 0, sizeof(array)); 466 if (!rc_json_get_required_array(num_entries, &array, response, field, field_name)) 467 return RC_MISSING_VALUE; 468 469 if (*num_entries) { 470 *entries = (uint32_t*)rc_buffer_alloc(&response->buffer, *num_entries * sizeof(uint32_t)); 471 if (!*entries) 472 return RC_OUT_OF_MEMORY; 473 474 value.name = field_name; 475 476 memset(&iterator, 0, sizeof(iterator)); 477 iterator.json = array.value_start; 478 iterator.end = array.value_end; 479 480 entry = *entries; 481 while (rc_json_get_array_entry_value(&value, &iterator)) { 482 if (!rc_json_get_unum(entry, &value, field_name)) 483 return RC_MISSING_VALUE; 484 485 ++entry; 486 } 487 } 488 else { 489 *entries = NULL; 490 } 491 492 return RC_OK; 493 } 494 495 int rc_json_get_required_array(uint32_t* num_entries, rc_json_field_t* array_field, rc_api_response_t* response, const rc_json_field_t* field, const char* field_name) { 496 #ifndef NDEBUG 497 if (strcmp(field->name, field_name) != 0) 498 return 0; 499 #endif 500 501 if (!rc_json_get_optional_array(num_entries, array_field, field, field_name)) 502 return rc_json_missing_field(response, field); 503 504 return 1; 505 } 506 507 int rc_json_get_optional_array(uint32_t* num_entries, rc_json_field_t* array_field, const rc_json_field_t* field, const char* field_name) { 508 #ifndef NDEBUG 509 if (strcmp(field->name, field_name) != 0) 510 return 0; 511 #else 512 (void)field_name; 513 #endif 514 515 if (!field->value_start || *field->value_start != '[') { 516 *num_entries = 0; 517 return 0; 518 } 519 520 memcpy(array_field, field, sizeof(*array_field)); 521 ++array_field->value_start; /* skip [ */ 522 523 *num_entries = field->array_size; 524 return 1; 525 } 526 527 int rc_json_get_array_entry_object(rc_json_field_t* fields, size_t field_count, rc_json_iterator_t* iterator) { 528 rc_json_skip_whitespace(iterator); 529 530 if (iterator->json >= iterator->end) 531 return 0; 532 533 if (rc_json_parse_object(iterator, fields, field_count, NULL) != RC_OK) 534 return 0; 535 536 rc_json_skip_whitespace(iterator); 537 538 if (!rc_json_match_char(iterator, ',')) 539 rc_json_match_char(iterator, ']'); 540 541 return 1; 542 } 543 544 static uint32_t rc_json_decode_hex4(const char* input) { 545 char hex[5]; 546 547 memcpy(hex, input, 4); 548 hex[4] = '\0'; 549 550 return (uint32_t)strtoul(hex, NULL, 16); 551 } 552 553 static int rc_json_ucs32_to_utf8(uint8_t* dst, uint32_t ucs32_char) { 554 if (ucs32_char < 0x80) { 555 dst[0] = (ucs32_char & 0x7F); 556 return 1; 557 } 558 559 if (ucs32_char < 0x0800) { 560 dst[1] = 0x80 | (ucs32_char & 0x3F); ucs32_char >>= 6; 561 dst[0] = 0xC0 | (ucs32_char & 0x1F); 562 return 2; 563 } 564 565 if (ucs32_char < 0x010000) { 566 dst[2] = 0x80 | (ucs32_char & 0x3F); ucs32_char >>= 6; 567 dst[1] = 0x80 | (ucs32_char & 0x3F); ucs32_char >>= 6; 568 dst[0] = 0xE0 | (ucs32_char & 0x0F); 569 return 3; 570 } 571 572 if (ucs32_char < 0x200000) { 573 dst[3] = 0x80 | (ucs32_char & 0x3F); ucs32_char >>= 6; 574 dst[2] = 0x80 | (ucs32_char & 0x3F); ucs32_char >>= 6; 575 dst[1] = 0x80 | (ucs32_char & 0x3F); ucs32_char >>= 6; 576 dst[0] = 0xF0 | (ucs32_char & 0x07); 577 return 4; 578 } 579 580 if (ucs32_char < 0x04000000) { 581 dst[4] = 0x80 | (ucs32_char & 0x3F); ucs32_char >>= 6; 582 dst[3] = 0x80 | (ucs32_char & 0x3F); ucs32_char >>= 6; 583 dst[2] = 0x80 | (ucs32_char & 0x3F); ucs32_char >>= 6; 584 dst[1] = 0x80 | (ucs32_char & 0x3F); ucs32_char >>= 6; 585 dst[0] = 0xF8 | (ucs32_char & 0x03); 586 return 5; 587 } 588 589 dst[5] = 0x80 | (ucs32_char & 0x3F); ucs32_char >>= 6; 590 dst[4] = 0x80 | (ucs32_char & 0x3F); ucs32_char >>= 6; 591 dst[3] = 0x80 | (ucs32_char & 0x3F); ucs32_char >>= 6; 592 dst[2] = 0x80 | (ucs32_char & 0x3F); ucs32_char >>= 6; 593 dst[1] = 0x80 | (ucs32_char & 0x3F); ucs32_char >>= 6; 594 dst[0] = 0xFC | (ucs32_char & 0x01); 595 return 6; 596 } 597 598 int rc_json_get_string(const char** out, rc_buffer_t* buffer, const rc_json_field_t* field, const char* field_name) { 599 const char* src = field->value_start; 600 size_t len = field->value_end - field->value_start; 601 char* dst; 602 603 #ifndef NDEBUG 604 if (strcmp(field->name, field_name) != 0) 605 return 0; 606 #else 607 (void)field_name; 608 #endif 609 610 if (!src) { 611 *out = NULL; 612 return 0; 613 } 614 615 if (len == 4 && memcmp(field->value_start, "null", 4) == 0) { 616 *out = NULL; 617 return 1; 618 } 619 620 if (*src == '\"') { 621 ++src; 622 623 if (*src == '\"') { 624 /* simple optimization for empty string - don't allocate space */ 625 *out = ""; 626 return 1; 627 } 628 629 *out = dst = (char*)rc_buffer_reserve(buffer, len - 1); /* -2 for quotes, +1 for null terminator */ 630 631 do { 632 if (*src == '\\') { 633 ++src; 634 if (*src == 'n') { 635 /* newline */ 636 ++src; 637 *dst++ = '\n'; 638 continue; 639 } 640 641 if (*src == 'r') { 642 /* carriage return */ 643 ++src; 644 *dst++ = '\r'; 645 continue; 646 } 647 648 if (*src == 'u') { 649 /* unicode character */ 650 uint32_t ucs32_char = rc_json_decode_hex4(src + 1); 651 src += 5; 652 653 if (ucs32_char >= 0xD800 && ucs32_char < 0xE000) { 654 /* surrogate lead - look for surrogate tail */ 655 if (ucs32_char < 0xDC00 && src[0] == '\\' && src[1] == 'u') { 656 const uint32_t surrogate = rc_json_decode_hex4(src + 2); 657 src += 6; 658 659 if (surrogate >= 0xDC00 && surrogate < 0xE000) { 660 /* found a surrogate tail, merge them */ 661 ucs32_char = (((ucs32_char - 0xD800) << 10) | (surrogate - 0xDC00)) + 0x10000; 662 } 663 } 664 665 if (!(ucs32_char & 0xFFFF0000)) { 666 /* invalid surrogate pair, fallback to replacement char */ 667 ucs32_char = 0xFFFD; 668 } 669 } 670 671 dst += rc_json_ucs32_to_utf8((unsigned char*)dst, ucs32_char); 672 continue; 673 } 674 675 if (*src == 't') { 676 /* tab */ 677 ++src; 678 *dst++ = '\t'; 679 continue; 680 } 681 682 /* just an escaped character, fallthrough to normal copy */ 683 } 684 685 *dst++ = *src++; 686 } while (*src != '\"'); 687 688 } else { 689 *out = dst = (char*)rc_buffer_reserve(buffer, len + 1); /* +1 for null terminator */ 690 memcpy(dst, src, len); 691 dst += len; 692 } 693 694 *dst++ = '\0'; 695 rc_buffer_consume(buffer, (uint8_t*)(*out), (uint8_t*)dst); 696 return 1; 697 } 698 699 void rc_json_get_optional_string(const char** out, rc_api_response_t* response, const rc_json_field_t* field, const char* field_name, const char* default_value) { 700 if (!rc_json_get_string(out, &response->buffer, field, field_name)) 701 *out = default_value; 702 } 703 704 int rc_json_get_required_string(const char** out, rc_api_response_t* response, const rc_json_field_t* field, const char* field_name) { 705 if (rc_json_get_string(out, &response->buffer, field, field_name)) 706 return 1; 707 708 return rc_json_missing_field(response, field); 709 } 710 711 int rc_json_get_num(int32_t* out, const rc_json_field_t* field, const char* field_name) { 712 const char* src = field->value_start; 713 int32_t value = 0; 714 int negative = 0; 715 716 #ifndef NDEBUG 717 if (strcmp(field->name, field_name) != 0) 718 return 0; 719 #else 720 (void)field_name; 721 #endif 722 723 if (!src) { 724 *out = 0; 725 return 0; 726 } 727 728 /* assert: string contains only numerals and an optional sign per rc_json_parse_field */ 729 if (*src == '-') { 730 negative = 1; 731 ++src; 732 } else if (*src == '+') { 733 ++src; 734 } else if (*src < '0' || *src > '9') { 735 *out = 0; 736 return 0; 737 } 738 739 while (src < field->value_end && *src != '.') { 740 value *= 10; 741 value += *src - '0'; 742 ++src; 743 } 744 745 if (negative) 746 *out = -value; 747 else 748 *out = value; 749 750 return 1; 751 } 752 753 void rc_json_get_optional_num(int32_t* out, const rc_json_field_t* field, const char* field_name, int default_value) { 754 if (!rc_json_get_num(out, field, field_name)) 755 *out = default_value; 756 } 757 758 int rc_json_get_required_num(int32_t* out, rc_api_response_t* response, const rc_json_field_t* field, const char* field_name) { 759 if (rc_json_get_num(out, field, field_name)) 760 return 1; 761 762 return rc_json_missing_field(response, field); 763 } 764 765 int rc_json_get_unum(uint32_t* out, const rc_json_field_t* field, const char* field_name) { 766 const char* src = field->value_start; 767 uint32_t value = 0; 768 769 #ifndef NDEBUG 770 if (strcmp(field->name, field_name) != 0) 771 return 0; 772 #else 773 (void)field_name; 774 #endif 775 776 if (!src) { 777 *out = 0; 778 return 0; 779 } 780 781 if (*src < '0' || *src > '9') { 782 *out = 0; 783 return 0; 784 } 785 786 /* assert: string contains only numerals per rc_json_parse_field */ 787 while (src < field->value_end && *src != '.') { 788 value *= 10; 789 value += *src - '0'; 790 ++src; 791 } 792 793 *out = value; 794 return 1; 795 } 796 797 void rc_json_get_optional_unum(uint32_t* out, const rc_json_field_t* field, const char* field_name, uint32_t default_value) { 798 if (!rc_json_get_unum(out, field, field_name)) 799 *out = default_value; 800 } 801 802 int rc_json_get_required_unum(uint32_t* out, rc_api_response_t* response, const rc_json_field_t* field, const char* field_name) { 803 if (rc_json_get_unum(out, field, field_name)) 804 return 1; 805 806 return rc_json_missing_field(response, field); 807 } 808 809 int rc_json_get_float(float* out, const rc_json_field_t* field, const char* field_name) { 810 int32_t whole, fraction, fraction_denominator; 811 const char* decimal = field->value_start; 812 813 if (!decimal) { 814 *out = 0.0f; 815 return 0; 816 } 817 818 if (!rc_json_get_num(&whole, field, field_name)) 819 return 0; 820 821 while (decimal < field->value_end && *decimal != '.') 822 ++decimal; 823 824 fraction = 0; 825 fraction_denominator = 1; 826 if (decimal) { 827 ++decimal; 828 while (decimal < field->value_end && *decimal >= '0' && *decimal <= '9') { 829 fraction *= 10; 830 fraction += *decimal - '0'; 831 fraction_denominator *= 10; 832 ++decimal; 833 } 834 } 835 836 if (whole < 0) 837 fraction = -fraction; 838 839 *out = (float)whole + ((float)fraction / (float)fraction_denominator); 840 return 1; 841 } 842 843 void rc_json_get_optional_float(float* out, const rc_json_field_t* field, const char* field_name, float default_value) { 844 if (!rc_json_get_float(out, field, field_name)) 845 *out = default_value; 846 } 847 848 int rc_json_get_required_float(float* out, rc_api_response_t* response, const rc_json_field_t* field, const char* field_name) { 849 if (rc_json_get_float(out, field, field_name)) 850 return 1; 851 852 return rc_json_missing_field(response, field); 853 } 854 855 int rc_json_get_datetime(time_t* out, const rc_json_field_t* field, const char* field_name) { 856 struct tm tm; 857 858 #ifndef NDEBUG 859 if (strcmp(field->name, field_name) != 0) 860 return 0; 861 #else 862 (void)field_name; 863 #endif 864 865 if (*field->value_start == '\"') { 866 memset(&tm, 0, sizeof(tm)); 867 if (sscanf_s(field->value_start + 1, "%d-%d-%d %d:%d:%d", /* DB format "2013-10-20 22:12:21" */ 868 &tm.tm_year, &tm.tm_mon, &tm.tm_mday, &tm.tm_hour, &tm.tm_min, &tm.tm_sec) == 6 || 869 /* NOTE: relies on sscanf stopping when it sees a non-digit after the seconds. could be 'Z', '.', '+', or '-' */ 870 sscanf_s(field->value_start + 1, "%d-%d-%dT%d:%d:%d", /* ISO format "2013-10-20T22:12:21.000000Z */ 871 &tm.tm_year, &tm.tm_mon, &tm.tm_mday, &tm.tm_hour, &tm.tm_min, &tm.tm_sec) == 6) { 872 tm.tm_mon--; /* 0-based */ 873 tm.tm_year -= 1900; /* 1900 based */ 874 875 /* mktime converts a struct tm to a time_t using the local timezone. 876 * the input string is UTC. since timegm is not universally cross-platform, 877 * figure out the offset between UTC and local time by applying the 878 * timezone conversion twice and manually removing the difference */ 879 { 880 time_t local_timet = mktime(&tm); 881 time_t skewed_timet, tz_offset; 882 struct tm gmt_tm; 883 gmtime_s(&gmt_tm, &local_timet); 884 skewed_timet = mktime(&gmt_tm); /* applies local time adjustment second time */ 885 tz_offset = skewed_timet - local_timet; 886 *out = local_timet - tz_offset; 887 } 888 889 return 1; 890 } 891 } 892 893 *out = 0; 894 return 0; 895 } 896 897 int rc_json_get_required_datetime(time_t* out, rc_api_response_t* response, const rc_json_field_t* field, const char* field_name) { 898 if (rc_json_get_datetime(out, field, field_name)) 899 return 1; 900 901 return rc_json_missing_field(response, field); 902 } 903 904 int rc_json_get_bool(int* out, const rc_json_field_t* field, const char* field_name) { 905 const char* src = field->value_start; 906 907 #ifndef NDEBUG 908 if (strcmp(field->name, field_name) != 0) 909 return 0; 910 #else 911 (void)field_name; 912 #endif 913 914 if (src) { 915 const size_t len = field->value_end - field->value_start; 916 if (len == 4 && strncasecmp(src, "true", 4) == 0) { 917 *out = 1; 918 return 1; 919 } else if (len == 5 && strncasecmp(src, "false", 5) == 0) { 920 *out = 0; 921 return 1; 922 } else if (len == 1) { 923 *out = (*src != '0'); 924 return 1; 925 } 926 } 927 928 *out = 0; 929 return 0; 930 } 931 932 void rc_json_get_optional_bool(int* out, const rc_json_field_t* field, const char* field_name, int default_value) { 933 if (!rc_json_get_bool(out, field, field_name)) 934 *out = default_value; 935 } 936 937 int rc_json_get_required_bool(int* out, rc_api_response_t* response, const rc_json_field_t* field, const char* field_name) { 938 if (rc_json_get_bool(out, field, field_name)) 939 return 1; 940 941 return rc_json_missing_field(response, field); 942 } 943 944 void rc_json_extract_filename(rc_json_field_t* field) { 945 if (field->value_end) { 946 const char* str = field->value_end; 947 948 /* remove the extension */ 949 while (str > field->value_start && str[-1] != '/') { 950 --str; 951 if (*str == '.') { 952 field->value_end = str; 953 break; 954 } 955 } 956 957 /* find the path separator */ 958 while (str > field->value_start && str[-1] != '/') 959 --str; 960 961 field->value_start = str; 962 } 963 } 964 965 /* --- rc_api_request --- */ 966 967 void rc_api_destroy_request(rc_api_request_t* request) 968 { 969 rc_buffer_destroy(&request->buffer); 970 } 971 972 /* --- rc_url_builder --- */ 973 974 void rc_url_builder_init(rc_api_url_builder_t* builder, rc_buffer_t* buffer, size_t estimated_size) { 975 rc_buffer_chunk_t* used_buffer; 976 977 memset(builder, 0, sizeof(*builder)); 978 builder->buffer = buffer; 979 builder->write = builder->start = (char*)rc_buffer_reserve(buffer, estimated_size); 980 981 used_buffer = &buffer->chunk; 982 while (used_buffer && used_buffer->write != (uint8_t*)builder->write) 983 used_buffer = used_buffer->next; 984 985 builder->end = (used_buffer) ? (char*)used_buffer->end : builder->start + estimated_size; 986 } 987 988 const char* rc_url_builder_finalize(rc_api_url_builder_t* builder) { 989 rc_url_builder_append(builder, "", 1); 990 991 if (builder->result != RC_OK) 992 return NULL; 993 994 rc_buffer_consume(builder->buffer, (uint8_t*)builder->start, (uint8_t*)builder->write); 995 return builder->start; 996 } 997 998 static int rc_url_builder_reserve(rc_api_url_builder_t* builder, size_t amount) { 999 if (builder->result == RC_OK) { 1000 size_t remaining = builder->end - builder->write; 1001 if (remaining < amount) { 1002 const size_t used = builder->write - builder->start; 1003 const size_t current_size = builder->end - builder->start; 1004 const size_t buffer_prefix_size = sizeof(rc_buffer_chunk_t); 1005 char* new_start; 1006 size_t new_size = (current_size < 256) ? 256 : current_size * 2; 1007 do { 1008 remaining = new_size - used; 1009 if (remaining >= amount) 1010 break; 1011 1012 new_size *= 2; 1013 } while (1); 1014 1015 /* rc_buffer_reserve will align to 256 bytes after including the buffer prefix. attempt to account for that */ 1016 if ((remaining - amount) > buffer_prefix_size) 1017 new_size -= buffer_prefix_size; 1018 1019 new_start = (char*)rc_buffer_reserve(builder->buffer, new_size); 1020 if (!new_start) { 1021 builder->result = RC_OUT_OF_MEMORY; 1022 return RC_OUT_OF_MEMORY; 1023 } 1024 1025 if (new_start != builder->start) { 1026 memcpy(new_start, builder->start, used); 1027 builder->start = new_start; 1028 builder->write = new_start + used; 1029 } 1030 1031 builder->end = builder->start + new_size; 1032 } 1033 } 1034 1035 return builder->result; 1036 } 1037 1038 void rc_url_builder_append_encoded_str(rc_api_url_builder_t* builder, const char* str) { 1039 static const char hex[] = "0123456789abcdef"; 1040 const char* start = str; 1041 size_t len = 0; 1042 for (;;) { 1043 const char c = *str++; 1044 switch (c) { 1045 case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': case 'i': case 'j': 1046 case 'k': case 'l': case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': case 's': case 't': 1047 case 'u': case 'v': case 'w': case 'x': case 'y': case 'z': 1048 case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': case 'I': case 'J': 1049 case 'K': case 'L': case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': 1050 case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z': 1051 case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': 1052 case '-': case '_': case '.': case '~': 1053 len++; 1054 continue; 1055 1056 case '\0': 1057 if (len) 1058 rc_url_builder_append(builder, start, len); 1059 1060 return; 1061 1062 default: 1063 if (rc_url_builder_reserve(builder, len + 3) != RC_OK) 1064 return; 1065 1066 if (len) { 1067 memcpy(builder->write, start, len); 1068 builder->write += len; 1069 } 1070 1071 if (c == ' ') { 1072 *builder->write++ = '+'; 1073 } else { 1074 *builder->write++ = '%'; 1075 *builder->write++ = hex[((unsigned char)c) >> 4]; 1076 *builder->write++ = hex[c & 0x0F]; 1077 } 1078 break; 1079 } 1080 1081 start = str; 1082 len = 0; 1083 } 1084 } 1085 1086 void rc_url_builder_append(rc_api_url_builder_t* builder, const char* data, size_t len) { 1087 if (rc_url_builder_reserve(builder, len) == RC_OK) { 1088 memcpy(builder->write, data, len); 1089 builder->write += len; 1090 } 1091 } 1092 1093 static int rc_url_builder_append_param_equals(rc_api_url_builder_t* builder, const char* param) { 1094 size_t param_len = strlen(param); 1095 1096 if (rc_url_builder_reserve(builder, param_len + 2) == RC_OK) { 1097 if (builder->write > builder->start) { 1098 if (builder->write[-1] != '?') 1099 *builder->write++ = '&'; 1100 } 1101 1102 memcpy(builder->write, param, param_len); 1103 builder->write += param_len; 1104 *builder->write++ = '='; 1105 } 1106 1107 return builder->result; 1108 } 1109 1110 void rc_url_builder_append_unum_param(rc_api_url_builder_t* builder, const char* param, uint32_t value) { 1111 if (rc_url_builder_append_param_equals(builder, param) == RC_OK) { 1112 char num[16]; 1113 int chars = snprintf(num, sizeof(num), "%u", value); 1114 rc_url_builder_append(builder, num, chars); 1115 } 1116 } 1117 1118 void rc_url_builder_append_num_param(rc_api_url_builder_t* builder, const char* param, int32_t value) { 1119 if (rc_url_builder_append_param_equals(builder, param) == RC_OK) { 1120 char num[16]; 1121 int chars = snprintf(num, sizeof(num), "%d", value); 1122 rc_url_builder_append(builder, num, chars); 1123 } 1124 } 1125 1126 void rc_url_builder_append_str_param(rc_api_url_builder_t* builder, const char* param, const char* value) { 1127 rc_url_builder_append_param_equals(builder, param); 1128 rc_url_builder_append_encoded_str(builder, value); 1129 } 1130 1131 void rc_api_url_build_dorequest_url(rc_api_request_t* request) { 1132 #define DOREQUEST_ENDPOINT "/dorequest.php" 1133 rc_buffer_init(&request->buffer); 1134 1135 if (!g_host) { 1136 request->url = RETROACHIEVEMENTS_HOST DOREQUEST_ENDPOINT; 1137 } 1138 else { 1139 const size_t endpoint_len = sizeof(DOREQUEST_ENDPOINT); 1140 const size_t host_len = strlen(g_host); 1141 const size_t url_len = host_len + endpoint_len; 1142 uint8_t* url = rc_buffer_reserve(&request->buffer, url_len); 1143 1144 memcpy(url, g_host, host_len); 1145 memcpy(url + host_len, DOREQUEST_ENDPOINT, endpoint_len); 1146 rc_buffer_consume(&request->buffer, url, url + url_len); 1147 1148 request->url = (char*)url; 1149 } 1150 #undef DOREQUEST_ENDPOINT 1151 } 1152 1153 int rc_api_url_build_dorequest(rc_api_url_builder_t* builder, const char* api, const char* username, const char* api_token) { 1154 if (!username || !*username || !api_token || !*api_token) { 1155 builder->result = RC_INVALID_STATE; 1156 return 0; 1157 } 1158 1159 rc_url_builder_append_str_param(builder, "r", api); 1160 rc_url_builder_append_str_param(builder, "u", username); 1161 rc_url_builder_append_str_param(builder, "t", api_token); 1162 1163 return (builder->result == RC_OK); 1164 } 1165 1166 /* --- Set Host --- */ 1167 1168 static void rc_api_update_host(char** host, const char* hostname) { 1169 if (*host != NULL) 1170 free(*host); 1171 1172 if (hostname != NULL) { 1173 if (strstr(hostname, "://")) { 1174 *host = strdup(hostname); 1175 } 1176 else { 1177 const size_t hostname_len = strlen(hostname); 1178 if (hostname_len == 0) { 1179 *host = NULL; 1180 } 1181 else { 1182 char* newhost = (char*)malloc(hostname_len + 7 + 1); 1183 if (newhost) { 1184 memcpy(newhost, "http://", 7); 1185 memcpy(&newhost[7], hostname, hostname_len + 1); 1186 *host = newhost; 1187 } 1188 else { 1189 *host = NULL; 1190 } 1191 } 1192 } 1193 } 1194 else { 1195 *host = NULL; 1196 } 1197 } 1198 1199 void rc_api_set_host(const char* hostname) { 1200 if (hostname && strcmp(hostname, RETROACHIEVEMENTS_HOST) == 0) 1201 hostname = NULL; 1202 1203 rc_api_update_host(&g_host, hostname); 1204 1205 if (!hostname) { 1206 /* also clear out the image hostname */ 1207 rc_api_set_image_host(NULL); 1208 } 1209 else if (strcmp(hostname, RETROACHIEVEMENTS_HOST_NONSSL) == 0) { 1210 /* if just pointing at the non-HTTPS host, explicitly use the default image host 1211 * so it doesn't try to use the web host directly */ 1212 rc_api_set_image_host(RETROACHIEVEMENTS_IMAGE_HOST_NONSSL); 1213 } 1214 } 1215 1216 void rc_api_set_image_host(const char* hostname) { 1217 rc_api_update_host(&g_imagehost, hostname); 1218 } 1219 1220 /* --- Fetch Image --- */ 1221 1222 int rc_api_init_fetch_image_request(rc_api_request_t* request, const rc_api_fetch_image_request_t* api_params) { 1223 rc_api_url_builder_t builder; 1224 1225 rc_buffer_init(&request->buffer); 1226 rc_url_builder_init(&builder, &request->buffer, 64); 1227 1228 if (g_imagehost) { 1229 rc_url_builder_append(&builder, g_imagehost, strlen(g_imagehost)); 1230 } 1231 else if (g_host) { 1232 rc_url_builder_append(&builder, g_host, strlen(g_host)); 1233 } 1234 else { 1235 rc_url_builder_append(&builder, RETROACHIEVEMENTS_IMAGE_HOST, sizeof(RETROACHIEVEMENTS_IMAGE_HOST) - 1); 1236 } 1237 1238 switch (api_params->image_type) 1239 { 1240 case RC_IMAGE_TYPE_GAME: 1241 rc_url_builder_append(&builder, "/Images/", 8); 1242 rc_url_builder_append(&builder, api_params->image_name, strlen(api_params->image_name)); 1243 rc_url_builder_append(&builder, ".png", 4); 1244 break; 1245 1246 case RC_IMAGE_TYPE_ACHIEVEMENT: 1247 rc_url_builder_append(&builder, "/Badge/", 7); 1248 rc_url_builder_append(&builder, api_params->image_name, strlen(api_params->image_name)); 1249 rc_url_builder_append(&builder, ".png", 4); 1250 break; 1251 1252 case RC_IMAGE_TYPE_ACHIEVEMENT_LOCKED: 1253 rc_url_builder_append(&builder, "/Badge/", 7); 1254 rc_url_builder_append(&builder, api_params->image_name, strlen(api_params->image_name)); 1255 rc_url_builder_append(&builder, "_lock.png", 9); 1256 break; 1257 1258 case RC_IMAGE_TYPE_USER: 1259 rc_url_builder_append(&builder, "/UserPic/", 9); 1260 rc_url_builder_append(&builder, api_params->image_name, strlen(api_params->image_name)); 1261 rc_url_builder_append(&builder, ".png", 4); 1262 break; 1263 1264 default: 1265 return RC_INVALID_STATE; 1266 } 1267 1268 request->url = rc_url_builder_finalize(&builder); 1269 request->post_data = NULL; 1270 1271 return builder.result; 1272 }