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