duckstation

duckstation, but archived from the revision just before upstream changed it to a proprietary software project, this version is the libre one
git clone https://git.neptards.moe/u3shit/duckstation.git
Log | Files | Refs | README | LICENSE

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 }