mirror of
https://github.com/Cesarus85/ringfall-mjolnir-vr.git
synced 2026-10-28 17:00:36 +01:00
599 lines
25 KiB
C
599 lines
25 KiB
C
/*
|
|
SERVER_JSON.C (test)
|
|
|
|
The real JSON of a dedicated server (port/linux/src/server_json.c): the
|
|
writer's escaping, nesting and values, the reader over the vendored jsmn
|
|
(its limits on depth and tokens, members, elements, strings unescaped) and
|
|
base64. test_server_json.py takes the whole file (under_test.inc) and its
|
|
header (server_json.h).
|
|
*/
|
|
|
|
#include "harness.h"
|
|
|
|
#include <float.h>
|
|
#include <limits.h>
|
|
#include <math.h>
|
|
|
|
#include "under_test.inc"
|
|
|
|
/* what a writer wrote: one call's text, into a static copy */
|
|
static const char *written(struct server_json *json)
|
|
{
|
|
static char text[4096];
|
|
|
|
snprintf(text, sizeof(text), "%s", server_json_text(json));
|
|
server_json_free(json);
|
|
return text;
|
|
}
|
|
|
|
/* text as ASCII, for a failure's message (the harness reads it as UTF-8) */
|
|
static const char *visible(const char *text)
|
|
{
|
|
static char shown[4096];
|
|
int length = 0;
|
|
|
|
for (; *text && length < (int)sizeof(shown) - 5; text++)
|
|
{
|
|
if ((unsigned char)*text >= 0x20 && (unsigned char)*text < 0x7F)
|
|
shown[length++] = *text;
|
|
else
|
|
length += snprintf(shown + length, 5, "\\x%02x", (unsigned char)*text);
|
|
}
|
|
shown[length] = 0;
|
|
return shown;
|
|
}
|
|
|
|
static const char *escaped(const char *value)
|
|
{
|
|
struct server_json json;
|
|
|
|
server_json_begin(&json);
|
|
server_json_string(&json, NULL, value);
|
|
return written(&json);
|
|
}
|
|
|
|
static const char *escaped_length(const char *value, int length)
|
|
{
|
|
struct server_json json;
|
|
|
|
server_json_begin(&json);
|
|
server_json_string_length(&json, NULL, value, length);
|
|
return written(&json);
|
|
}
|
|
|
|
static int parse(struct server_json_document *document, const char *text, int maximum_depth)
|
|
{
|
|
return server_json_parse(document, text, (int)strlen(text), maximum_depth);
|
|
}
|
|
|
|
/* whether text parses (at a depth of 16, the API's) */
|
|
static int parses(const char *text)
|
|
{
|
|
struct server_json_document document;
|
|
int ok = parse(&document, text, 16);
|
|
|
|
server_json_document_free(&document);
|
|
return ok;
|
|
}
|
|
|
|
/* the string at the top of a document ("..." as text), unescaped into a
|
|
buffer of size bytes: 1 if it fits */
|
|
static int unescaped(const char *text, char *value, int size)
|
|
{
|
|
struct server_json_document document;
|
|
char wrapped[256];
|
|
int ok;
|
|
|
|
snprintf(wrapped, sizeof(wrapped), "[%s]", text);
|
|
if (!parse(&document, wrapped, 16))
|
|
return -1;
|
|
ok = server_json_get_string(&document, 1, value, size);
|
|
server_json_document_free(&document);
|
|
return ok;
|
|
}
|
|
|
|
static int same_string(const char *text, const char *expected)
|
|
{
|
|
char value[256];
|
|
|
|
return unescaped(text, value, sizeof(value)) == 1 && !strcmp(value, expected);
|
|
}
|
|
|
|
static int decoded(const char *text, const char *expected, int maximum)
|
|
{
|
|
unsigned char bytes[64];
|
|
int count = server_base64_decode(text, bytes, maximum);
|
|
|
|
return count == (int)strlen(expected) && !memcmp(bytes, expected, (size_t)count);
|
|
}
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
char const *case_name = argc > 1 ? argv[1] : "";
|
|
|
|
/* quotes, backslashes and control characters escaped; UTF-8 kept as it
|
|
is, anything else U+FFFD */
|
|
CASE("escaping")
|
|
{
|
|
CHECK(!strcmp(escaped("plain text"), "\"plain text\""), "wrote %s", visible(escaped("plain text")));
|
|
CHECK(!strcmp(escaped("say \"hi\" \\o/"), "\"say \\\"hi\\\" \\\\o/\""), "wrote %s", visible(escaped("say \"hi\" \\o/")));
|
|
CHECK(!strcmp(escaped("a\nb\tc\rd"), "\"a\\nb\\tc\\rd\""), "wrote %s", visible(escaped("a\nb\tc\rd")));
|
|
CHECK(!strcmp(escaped("\x01\x1f\x08"), "\"\\u0001\\u001f\\u0008\""), "wrote %s", visible(escaped("\x01\x1f\x08")));
|
|
CHECK(!strcmp(escaped("del\x7f"), "\"del\x7f\""), "DEL was not written as it is: %s", visible(escaped("del\x7f")));
|
|
CHECK(!strcmp(escaped("caf\xc3\xa9 \xe2\x82\xac \xf0\x9f\x98\x80"), "\"caf\xc3\xa9 \xe2\x82\xac \xf0\x9f\x98\x80\""),
|
|
"UTF-8 was changed: %s", visible(escaped("caf\xc3\xa9 \xe2\x82\xac \xf0\x9f\x98\x80")));
|
|
/* (a lone continuation byte, an overlong lead, a sequence cut off,
|
|
a lead after a lead) */
|
|
CHECK(!strcmp(escaped("a\x80z"), "\"a\\ufffdz\""), "wrote %s", visible(escaped("a\x80z")));
|
|
CHECK(!strcmp(escaped("\xc0\xaf"), "\"\\ufffd\\ufffd\""), "wrote %s", visible(escaped("\xc0\xaf")));
|
|
CHECK(!strcmp(escaped("\xe2\x82"), "\"\\ufffd\\ufffd\""), "wrote %s", visible(escaped("\xe2\x82")));
|
|
CHECK(!strcmp(escaped("\xc3\xc3\xa9"), "\"\\ufffd\xc3\xa9\""), "wrote %s", visible(escaped("\xc3\xc3\xa9")));
|
|
CHECK(!strcmp(escaped("\xf8"), "\"\\ufffd\""), "wrote %s", visible(escaped("\xf8")));
|
|
/* (a sequence that runs past the length given is not taken whole) */
|
|
CHECK(!strcmp(escaped_length("\xc3\xa9", 1), "\"\\ufffd\""), "wrote %s", visible(escaped_length("\xc3\xa9", 1)));
|
|
CHECK(!strcmp(escaped_length("abcdef", 3), "\"abc\""), "wrote %s", visible(escaped_length("abcdef", 3)));
|
|
CHECK(!strcmp(escaped_length("ab\0cd", 5), "\"ab\""), "wrote past a NUL: %s", visible(escaped_length("ab\0cd", 5)));
|
|
CHECK(!strcmp(escaped(NULL), "null"), "wrote %s", visible(escaped(NULL)));
|
|
CHECK(!strcmp(escaped(""), "\"\""), "wrote %s", visible(escaped("")));
|
|
{
|
|
struct server_json json;
|
|
|
|
server_json_begin(&json);
|
|
server_json_object_begin(&json, NULL);
|
|
server_json_string(&json, "k\"ey\n", "v");
|
|
server_json_object_end(&json);
|
|
CHECK(!strcmp(written(&json), "{\"k\\\"ey\\n\":\"v\"}"), "a key was not escaped");
|
|
}
|
|
return 0;
|
|
}
|
|
/* bytes that can never begin UTF-8 (0xF5 to 0xFF) are not written as
|
|
they are */
|
|
CASE("utf8-invalid-leads")
|
|
{
|
|
CHECK(!strcmp(escaped("\xf5\x80\x80\x80"), "\"\\ufffd\\ufffd\\ufffd\\ufffd\""), "wrote %s",
|
|
visible(escaped("\xf5\x80\x80\x80")));
|
|
CHECK(!strcmp(escaped("\xff\x80\x80"), "\"\\ufffd\\ufffd\\ufffd\""), "wrote %s", visible(escaped("\xff\x80\x80")));
|
|
CHECK(!strcmp(escaped("\xf8\x88\x80\x80\x80"), "\"\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\""), "wrote %s",
|
|
visible(escaped("\xf8\x88\x80\x80\x80")));
|
|
return 0;
|
|
}
|
|
/* overlong forms and surrogates are not UTF-8 either */
|
|
CASE("utf8-overlong-surrogates")
|
|
{
|
|
CHECK(!strcmp(escaped("\xe0\x80\xaf"), "\"\\ufffd\\ufffd\\ufffd\""), "wrote %s", visible(escaped("\xe0\x80\xaf")));
|
|
CHECK(!strcmp(escaped("\xed\xa0\x80"), "\"\\ufffd\\ufffd\\ufffd\""), "wrote %s", visible(escaped("\xed\xa0\x80")));
|
|
CHECK(!strcmp(escaped("\xf4\x90\x80\x80"), "\"\\ufffd\\ufffd\\ufffd\\ufffd\""), "wrote %s",
|
|
visible(escaped("\xf4\x90\x80\x80")));
|
|
return 0;
|
|
}
|
|
/* members separated by commas, keys in objects, none in arrays */
|
|
CASE("nesting")
|
|
{
|
|
struct server_json json;
|
|
const char *text;
|
|
|
|
server_json_begin(&json);
|
|
server_json_object_begin(&json, NULL);
|
|
server_json_string(&json, "name", "x");
|
|
server_json_object_begin(&json, "empty");
|
|
server_json_object_end(&json);
|
|
server_json_array_begin(&json, "list");
|
|
server_json_integer(&json, NULL, 1);
|
|
server_json_array_begin(&json, NULL);
|
|
server_json_array_end(&json);
|
|
server_json_object_begin(&json, NULL);
|
|
server_json_boolean(&json, "b", 1);
|
|
server_json_null(&json, "n");
|
|
server_json_object_end(&json);
|
|
server_json_string(&json, NULL, "s");
|
|
server_json_array_end(&json);
|
|
server_json_raw(&json, "raw", "{\"x\":[1,2]}");
|
|
server_json_object_end(&json);
|
|
text = written(&json);
|
|
CHECK(!strcmp(text, "{\"name\":\"x\",\"empty\":{},\"list\":[1,[],{\"b\":true,\"n\":null},\"s\"],"
|
|
"\"raw\":{\"x\":[1,2]}}"), "wrote %s", text);
|
|
/* (what it writes, it reads) */
|
|
CHECK(parses(text), "its own text did not parse: %s", text);
|
|
|
|
server_json_begin(&json);
|
|
server_json_array_begin(&json, NULL);
|
|
server_json_array_end(&json);
|
|
CHECK(!strcmp(written(&json), "[]"), "an empty array was not []");
|
|
server_json_begin(&json);
|
|
CHECK(!strcmp(server_json_text(&json), ""), "nothing written was not \"\"");
|
|
server_json_free(&json);
|
|
return 0;
|
|
}
|
|
/* integers, reals (no infinity nor NaN), booleans, null and raw text */
|
|
CASE("values")
|
|
{
|
|
struct server_json json;
|
|
const char *text;
|
|
|
|
server_json_begin(&json);
|
|
server_json_array_begin(&json, NULL);
|
|
server_json_integer(&json, NULL, 0);
|
|
server_json_integer(&json, NULL, -42);
|
|
server_json_integer(&json, NULL, LLONG_MAX);
|
|
server_json_integer(&json, NULL, LLONG_MIN);
|
|
server_json_real(&json, NULL, 1.5);
|
|
server_json_real(&json, NULL, -0.1);
|
|
server_json_real(&json, NULL, 1e-7);
|
|
server_json_real(&json, NULL, NAN);
|
|
server_json_real(&json, NULL, INFINITY);
|
|
server_json_real(&json, NULL, -INFINITY);
|
|
server_json_real(&json, NULL, DBL_MAX);
|
|
server_json_boolean(&json, NULL, 0);
|
|
server_json_boolean(&json, NULL, 7);
|
|
server_json_null(&json, NULL);
|
|
server_json_raw(&json, NULL, "");
|
|
server_json_raw(&json, NULL, NULL);
|
|
server_json_raw(&json, NULL, "\"ready\"");
|
|
server_json_array_end(&json);
|
|
text = written(&json);
|
|
CHECK(!strcmp(text, "[0,-42,9223372036854775807,-9223372036854775808,1.5,-0.1,1e-07,null,null,null,null,"
|
|
"false,true,null,null,null,\"ready\"]"), "wrote %s", text);
|
|
CHECK(parses(text), "its own text did not parse: %s", text);
|
|
return 0;
|
|
}
|
|
/* 24 deep is written; deeper, nothing is */
|
|
CASE("write-depth")
|
|
{
|
|
struct server_json json;
|
|
int index;
|
|
|
|
server_json_begin(&json);
|
|
for (index = 0; index < SERVER_JSON_MAXIMUM_DEPTH; index++)
|
|
server_json_array_begin(&json, NULL);
|
|
for (index = 0; index < SERVER_JSON_MAXIMUM_DEPTH; index++)
|
|
server_json_array_end(&json);
|
|
CHECK(!json.failed && (int)strlen(server_json_text(&json)) == 2 * SERVER_JSON_MAXIMUM_DEPTH,
|
|
"%d deep was not written", SERVER_JSON_MAXIMUM_DEPTH);
|
|
server_json_free(&json);
|
|
|
|
server_json_begin(&json);
|
|
for (index = 0; index <= SERVER_JSON_MAXIMUM_DEPTH; index++)
|
|
server_json_object_begin(&json, index ? "k" : NULL);
|
|
for (index = 0; index <= SERVER_JSON_MAXIMUM_DEPTH; index++)
|
|
server_json_object_end(&json);
|
|
CHECK(json.failed && !strcmp(server_json_text(&json), ""), "%d deep was written",
|
|
SERVER_JSON_MAXIMUM_DEPTH + 1);
|
|
server_json_free(&json);
|
|
return 0;
|
|
}
|
|
/* text that would grow past SERVER_JSON_MAXIMUM_SIZE is not written */
|
|
CASE("write-size")
|
|
{
|
|
struct server_json json;
|
|
int size = SERVER_JSON_MAXIMUM_SIZE / 2;
|
|
char *large = malloc((size_t)size + 1);
|
|
|
|
memset(large, 'a', (size_t)size);
|
|
large[size] = 0;
|
|
server_json_begin(&json);
|
|
server_json_array_begin(&json, NULL);
|
|
server_json_string(&json, NULL, large);
|
|
CHECK(!json.failed && json.length == size + 3, "half the most was not written");
|
|
server_json_string(&json, NULL, large);
|
|
CHECK(json.failed && !strcmp(server_json_text(&json), ""), "more than the most was written");
|
|
/* (and once failed, nothing more is) */
|
|
server_json_array_end(&json);
|
|
CHECK(!strcmp(server_json_text(&json), ""), "a failed text was written to");
|
|
server_json_free(&json);
|
|
free(large);
|
|
return 0;
|
|
}
|
|
/* members, elements and values found, others refused */
|
|
CASE("read")
|
|
{
|
|
static const char text[] = "{\"ab\":1,\"a\":{\"x\":[10,{\"y\":null},\"s\"]},\"t\":true,\"f\":false,"
|
|
"\"n\":null,\"r\":-2.5e2,\"s\":\"str\"}trailing";
|
|
struct server_json_document document;
|
|
int a, x, element, value;
|
|
double number;
|
|
char string[16];
|
|
|
|
/* (the length given ends the text: what follows is not read) */
|
|
CHECK(server_json_parse(&document, text, (int)(strstr(text, "trailing") - text), 8), "a document was refused");
|
|
CHECK(document.tokens[0].type == _server_json_object, "the top is not an object");
|
|
CHECK(server_json_get_number(&document, server_json_member(&document, 0, "ab"), &number) && number == 1,
|
|
"ab was not 1");
|
|
a = server_json_member(&document, 0, "a");
|
|
CHECK(a > 0 && document.tokens[a].type == _server_json_object, "a was not found after ab");
|
|
CHECK(server_json_member(&document, 0, "") == -1 && server_json_member(&document, 0, "abc") == -1 &&
|
|
server_json_member(&document, 0, "y") == -1, "a member that is not there (or not here) was found");
|
|
x = server_json_member(&document, a, "x");
|
|
CHECK(x > 0 && document.tokens[x].type == _server_json_array && document.tokens[x].size == 3, "x was not found");
|
|
CHECK(server_json_get_number(&document, server_json_element(&document, x, 0), &number) && number == 10,
|
|
"x[0] was not 10");
|
|
element = server_json_element(&document, x, 1);
|
|
CHECK(server_json_is_null(&document, server_json_member(&document, element, "y")), "x[1].y was not null");
|
|
CHECK(server_json_get_string(&document, server_json_element(&document, x, 2), string, sizeof(string)) &&
|
|
!strcmp(string, "s"), "x[2] was not \"s\"");
|
|
CHECK(server_json_element(&document, x, 3) == -1 && server_json_element(&document, x, -1) == -1,
|
|
"an element past the array was found");
|
|
CHECK(server_json_element(&document, 0, 0) == -1 && server_json_member(&document, x, "x") == -1,
|
|
"an object was read as an array, or an array as an object");
|
|
CHECK(server_json_member(&document, -1, "a") == -1 && server_json_member(&document, 999, "a") == -1 &&
|
|
server_json_element(&document, 999, 0) == -1, "a token past the document was read");
|
|
/* (skipping a member's key skips its value with it) */
|
|
CHECK(server_json_skip(&document, a) == server_json_member(&document, 0, "t") - 1, "a was not skipped whole");
|
|
CHECK(server_json_skip(&document, 0) == document.count, "the document was not skipped whole");
|
|
CHECK(server_json_get_boolean(&document, server_json_member(&document, 0, "t"), &value) && value == 1,
|
|
"t was not true");
|
|
CHECK(server_json_get_boolean(&document, server_json_member(&document, 0, "f"), &value) && value == 0,
|
|
"f was not false");
|
|
CHECK(!server_json_get_boolean(&document, server_json_member(&document, 0, "n"), &value) &&
|
|
!server_json_get_boolean(&document, server_json_member(&document, 0, "ab"), &value),
|
|
"null or a number was a boolean");
|
|
CHECK(server_json_get_number(&document, server_json_member(&document, 0, "r"), &number) && number == -250,
|
|
"r was not -250");
|
|
CHECK(!server_json_get_number(&document, server_json_member(&document, 0, "t"), &number) &&
|
|
!server_json_get_number(&document, server_json_member(&document, 0, "s"), &number) &&
|
|
!server_json_get_number(&document, a, &number) && !server_json_get_number(&document, -1, &number),
|
|
"something that is not a number was one");
|
|
CHECK(!server_json_get_string(&document, server_json_member(&document, 0, "ab"), string, sizeof(string)) &&
|
|
!server_json_get_string(&document, -1, string, sizeof(string)), "a number was a string");
|
|
CHECK(!server_json_is_null(&document, server_json_member(&document, 0, "s")) &&
|
|
!server_json_is_null(&document, -1), "a string was null");
|
|
server_json_document_free(&document);
|
|
CHECK(!document.tokens && !document.count, "a freed document kept its tokens");
|
|
return 0;
|
|
}
|
|
/* strings unescaped, \uXXXX (and surrogate pairs) to UTF-8, into what fits */
|
|
CASE("strings")
|
|
{
|
|
char value[16];
|
|
|
|
CHECK(same_string("\"a\\nb\\tc\\rd\\be\\ff\"", "a\nb\tc\rd\be\ff"), "the short escapes were not read");
|
|
CHECK(same_string("\"\\\"\\\\\\/\"", "\"\\/"), "a quote, backslash or slash was not read");
|
|
CHECK(same_string("\"\\u0041\\u00e9\\u20AC\"", "A\xc3\xa9\xe2\x82\xac"), "\\u was not read to UTF-8");
|
|
CHECK(same_string("\"\\ud83d\\ude00!\"", "\xf0\x9f\x98\x80!"), "a surrogate pair was not read as one");
|
|
CHECK(same_string("\"\\uDBFF\\uDFFF\"", "\xf4\x8f\xbf\xbf"), "the last code point was not read");
|
|
CHECK(same_string("\"\\ud83dx\"", "\xef\xbf\xbdx"), "a lone high surrogate was not U+FFFD");
|
|
CHECK(same_string("\"\\ude00\"", "\xef\xbf\xbd"), "a lone low surrogate was not U+FFFD");
|
|
CHECK(same_string("\"\\ud83d\\u0041\"", "\xef\xbf\xbd" "A"), "a high surrogate before a letter was not U+FFFD");
|
|
CHECK(same_string("\"\\ud83d\\ud83d\\ude00\"", "\xef\xbf\xbd\xf0\x9f\x98\x80"),
|
|
"a high surrogate before a pair was not U+FFFD and the pair");
|
|
CHECK(same_string("\"caf\xc3\xa9\"", "caf\xc3\xa9"), "UTF-8 was changed");
|
|
CHECK(same_string("\"\"", ""), "an empty string was not read");
|
|
/* (exactly what fits, and a byte too many) */
|
|
CHECK(unescaped("\"1234567\"", value, 8) == 1 && !strcmp(value, "1234567"), "a string that fits was refused");
|
|
memset(value, '#', sizeof(value));
|
|
CHECK(unescaped("\"12345678\"", value, 8) == 0, "a string that does not fit was taken");
|
|
CHECK(value[8] == '#', "a string that does not fit was written past its room");
|
|
CHECK(unescaped("\"\\u20ac\"", value, 4) == 1 && unescaped("\"\\u20ac\"", value, 3) == 0,
|
|
"a character's bytes were split at the end of the room");
|
|
CHECK(unescaped("\"\"", value, 0) == 0, "a string was read into no room");
|
|
return 0;
|
|
}
|
|
/* text that is not JSON (as jsmn reads it strictly) is refused */
|
|
CASE("malformed")
|
|
{
|
|
static const char *const refused[] = {
|
|
"", "{", "[", "{\"a\":1", "[1,2", "{\"a\":\"b", "{\"a\" 1}", "{\"a\",1}", "{1:2}", "[1}", "{]",
|
|
"\"open", "[\"a\\x\"]", "[\"\\u12\"]", "[\"\\u12zz\"]", "{} x",
|
|
/* (a number, true or null at the top: jsmn's strict mode wants a
|
|
delimiter after one) */
|
|
"1", "true", "null",
|
|
/* (what jsmn's strict mode takes, the grammar's check does not) */
|
|
"[1,]", "{\"a\":1,}", "[1 2]", "{\"a\":1 \"b\":2}", "[,1]", "[\"a\":1]", "{\"a\":}", "{}{}", "[01]",
|
|
"[\"a\x01\"]", "[\"a\"\"b\"]", "[1e]", "[.5]", "[+1]", "[True]",
|
|
NULL,
|
|
};
|
|
struct server_json_document document;
|
|
int index;
|
|
|
|
for (index = 0; refused[index]; index++)
|
|
CHECK(!parses(refused[index]), "%s was taken", refused[index]);
|
|
CHECK(!server_json_parse(&document, NULL, 4, 8) && !server_json_parse(&document, "[1]", 0, 8) &&
|
|
!server_json_parse(&document, "[1]", -1, 8), "nothing, or a length of none, was taken");
|
|
/* (cut off anywhere, a document is refused) */
|
|
{
|
|
static const char whole[] = "{\"a\":[1,{\"b\":\"c\\u00e9\"}],\"d\":true}";
|
|
int length;
|
|
|
|
for (length = 1; length < (int)sizeof(whole) - 1; length++)
|
|
{
|
|
CHECK(!server_json_parse(&document, whole, length, 8), "%.*s was taken", length, whole);
|
|
CHECK(!document.tokens, "a refused document kept tokens");
|
|
}
|
|
CHECK(server_json_parse(&document, whole, length, 8), "the whole document was refused");
|
|
server_json_document_free(&document);
|
|
}
|
|
return 0;
|
|
}
|
|
/* what jsmn takes as a primitive (it reads only the first character) is
|
|
refused unless it is a number, boolean or null */
|
|
CASE("primitives")
|
|
{
|
|
static const char *const neither[] = { "[tru]", "[nul]", "[-]", "[nan]", "[truex]", "[nullx]", "[falsey]", "[1_0]",
|
|
"[0.5.5]", NULL };
|
|
struct server_json_document document;
|
|
double number;
|
|
int index, value;
|
|
|
|
for (index = 0; neither[index]; index++)
|
|
{
|
|
CHECK(!parse(&document, neither[index], 8), "%s was taken", neither[index]);
|
|
}
|
|
CHECK(parse(&document, "[-0.5e+1,1E2]", 8), "numbers were refused");
|
|
CHECK(server_json_get_number(&document, 1, &number) && number == -5, "-0.5e+1 was not -5");
|
|
CHECK(server_json_get_number(&document, 2, &number) && number == 100, "1E2 was not 100");
|
|
server_json_document_free(&document);
|
|
return 0;
|
|
}
|
|
/* only JSON's numbers are numbers: not strtod's NaN, infinities or hex,
|
|
which pass every range check a caller makes ("minutes", "time") */
|
|
CASE("numbers-json-only")
|
|
{
|
|
static const char *const refused[] = { "[-nan]", "[-inf]", "[-infinity]", "[0x10]", "[0x1p3]", "[1e999]", NULL };
|
|
struct server_json_document document;
|
|
double number;
|
|
int index;
|
|
|
|
for (index = 0; refused[index]; index++)
|
|
{
|
|
CHECK(!parse(&document, refused[index], 8) || !server_json_get_number(&document, 1, &number),
|
|
"%s was read as %g", refused[index], number);
|
|
server_json_document_free(&document);
|
|
}
|
|
return 0;
|
|
}
|
|
/* nesting deeper than asked is refused (each key a level, as jsmn makes
|
|
a key its value's parent) */
|
|
CASE("read-depth")
|
|
{
|
|
struct server_json_document document;
|
|
char deep[4096];
|
|
int index;
|
|
|
|
CHECK(parse(&document, "[[1]]", 2), "[[1]] was refused at a depth of 2");
|
|
server_json_document_free(&document);
|
|
CHECK(!parse(&document, "[[[1]]]", 2), "[[[1]]] was taken at a depth of 2");
|
|
CHECK(parse(&document, "{\"a\":1}", 2), "{\"a\":1} was refused at a depth of 2");
|
|
server_json_document_free(&document);
|
|
CHECK(!parse(&document, "{\"a\":[1]}", 2), "{\"a\":[1]} was taken at a depth of 2");
|
|
CHECK(parse(&document, "[]", 0), "[] was refused at a depth of 0");
|
|
server_json_document_free(&document);
|
|
CHECK(!parse(&document, "[1]", 0), "[1] was taken at a depth of 0");
|
|
/* (a thousand open brackets) */
|
|
for (index = 0; index < 1000; index++)
|
|
{
|
|
deep[index] = '[';
|
|
deep[1000 + index] = ']';
|
|
}
|
|
deep[2000] = 0;
|
|
CHECK(!parse(&document, deep, 16), "a thousand deep was taken");
|
|
CHECK(parse(&document, deep, 1000), "a thousand deep was refused at a depth of 1000");
|
|
server_json_document_free(&document);
|
|
return 0;
|
|
}
|
|
/* a document of SERVER_JSON_MAXIMUM_TOKENS is read, one of more is not */
|
|
CASE("tokens")
|
|
{
|
|
struct server_json_document document;
|
|
int elements = SERVER_JSON_MAXIMUM_TOKENS - 1;
|
|
char *text = malloc((size_t)(2 * elements + 8));
|
|
int index, length = 0;
|
|
|
|
text[length++] = '[';
|
|
for (index = 0; index < elements; index++)
|
|
{
|
|
text[length++] = '0';
|
|
text[length++] = ',';
|
|
}
|
|
text[length - 1] = ']';
|
|
CHECK(server_json_parse(&document, text, length, 8) && document.count == SERVER_JSON_MAXIMUM_TOKENS,
|
|
"%d tokens were refused", SERVER_JSON_MAXIMUM_TOKENS);
|
|
CHECK(server_json_element(&document, 0, elements - 1) == SERVER_JSON_MAXIMUM_TOKENS - 1,
|
|
"the last element was not found");
|
|
server_json_document_free(&document);
|
|
text[length - 1] = ',';
|
|
text[length++] = '0';
|
|
text[length++] = ']';
|
|
CHECK(!server_json_parse(&document, text, length, 8), "%d tokens were taken", SERVER_JSON_MAXIMUM_TOKENS + 1);
|
|
free(text);
|
|
return 0;
|
|
}
|
|
/* a master's list reads up to SERVER_JSON_MAXIMUM_LIST_TOKENS, a body
|
|
of the API still SERVER_JSON_MAXIMUM_TOKENS */
|
|
CASE("list-tokens")
|
|
{
|
|
struct server_json_document document;
|
|
int elements = SERVER_JSON_MAXIMUM_LIST_TOKENS - 1;
|
|
char *text = malloc((size_t)(2 * elements + 8));
|
|
int index, length = 0;
|
|
|
|
CHECK(SERVER_JSON_MAXIMUM_LIST_TOKENS > SERVER_JSON_MAXIMUM_TOKENS, "a list reads no more than a body");
|
|
text[length++] = '[';
|
|
for (index = 0; index < elements; index++)
|
|
{
|
|
text[length++] = '0';
|
|
text[length++] = ',';
|
|
}
|
|
text[length - 1] = ']';
|
|
CHECK(server_json_parse_tokens(&document, text, length, 8, SERVER_JSON_MAXIMUM_LIST_TOKENS) &&
|
|
document.count == SERVER_JSON_MAXIMUM_LIST_TOKENS, "a list of %d tokens was refused",
|
|
SERVER_JSON_MAXIMUM_LIST_TOKENS);
|
|
server_json_document_free(&document);
|
|
CHECK(!server_json_parse(&document, text, length, 8), "a body of %d tokens was taken",
|
|
SERVER_JSON_MAXIMUM_LIST_TOKENS);
|
|
text[length - 1] = ',';
|
|
text[length++] = '0';
|
|
text[length++] = ']';
|
|
CHECK(!server_json_parse_tokens(&document, text, length, 8, SERVER_JSON_MAXIMUM_LIST_TOKENS),
|
|
"a list of %d tokens was taken", SERVER_JSON_MAXIMUM_LIST_TOKENS + 1);
|
|
CHECK(!server_json_parse_tokens(&document, "[]", 2, 8, 0), "a document was read into no tokens");
|
|
free(text);
|
|
return 0;
|
|
}
|
|
/* \u0000 is refused (the text read would end there, past the checks of
|
|
what follows it); the other control characters are read, for the callers
|
|
to refuse */
|
|
CASE("nul-refused")
|
|
{
|
|
char value[16];
|
|
|
|
memset(value, '#', sizeof(value));
|
|
CHECK(unescaped("\"\\u0000x\"", value, sizeof(value)) == 0 && !value[0], "\\u0000x was read as \"%s\"",
|
|
visible(value));
|
|
CHECK(unescaped("\"x\\u0000\"", value, sizeof(value)) == 0 && !value[0], "x\\u0000 was read as \"%s\"",
|
|
visible(value));
|
|
CHECK(same_string("\"\\u0001\\u001f\\u0030\"", "\x01\x1f" "0"), "\\u0001, \\u001f or \\u0030 was not read");
|
|
return 0;
|
|
}
|
|
/* RFC 4648's examples, every length round trip, bad text refused, the
|
|
most bytes kept to */
|
|
CASE("base64")
|
|
{
|
|
static const char *const examples[][2] = {
|
|
{ "", "" }, { "f", "Zg==" }, { "fo", "Zm8=" }, { "foo", "Zm9v" }, { "foob", "Zm9vYg==" },
|
|
{ "fooba", "Zm9vYmE=" }, { "foobar", "Zm9vYmFy" },
|
|
};
|
|
static const char *const refused[] = {
|
|
"Zg=", "Zm9", "Zm9vY", "Zg=a", "Z===", "====", "Zg==Zg==", "Zm9v!A==", "Zm9v YQ=", "Zm9v\nYQ=", "Zm-v",
|
|
"Zm_v", "\xc3\xa9Zg", NULL,
|
|
};
|
|
unsigned char bytes[96], back[96];
|
|
char text[160];
|
|
int index, size;
|
|
|
|
for (index = 0; index < (int)(sizeof(examples) / sizeof(examples[0])); index++)
|
|
{
|
|
server_base64_encode((const unsigned char *)examples[index][0], (int)strlen(examples[index][0]), text);
|
|
CHECK(!strcmp(text, examples[index][1]), "%s was written %s", examples[index][0], text);
|
|
CHECK(decoded(examples[index][1], examples[index][0], 64), "%s was not read", examples[index][1]);
|
|
}
|
|
for (index = 0; index < (int)sizeof(bytes); index++)
|
|
bytes[index] = (unsigned char)(index * 151 + 7);
|
|
for (size = 0; size <= (int)sizeof(bytes); size++)
|
|
{
|
|
server_base64_encode(bytes, size, text);
|
|
CHECK((int)strlen(text) == 4 * ((size + 2) / 3), "%d bytes were written in %d characters", size,
|
|
(int)strlen(text));
|
|
CHECK(server_base64_decode(text, back, (int)sizeof(back)) == size && !memcmp(bytes, back, (size_t)size),
|
|
"%d bytes did not come back", size);
|
|
}
|
|
server_base64_encode((const unsigned char *)"\xfb\xff\xbf", 3, text);
|
|
CHECK(!strcmp(text, "+/+/"), "the last two characters were not + and /: %s", text);
|
|
for (index = 0; refused[index]; index++)
|
|
{
|
|
CHECK(server_base64_decode(refused[index], back, (int)sizeof(back)) == -1, "%s was read",
|
|
visible(refused[index]));
|
|
}
|
|
/* (the most is kept to exactly, padding counted out) */
|
|
memset(back, '#', sizeof(back));
|
|
CHECK(decoded("Zm9vYmFy", "foobar", 6) && server_base64_decode("Zm9vYmFy", back, 5) == -1,
|
|
"six bytes were not read into six, or were into five");
|
|
CHECK(decoded("Zm9vYg==", "foob", 4) && server_base64_decode("Zm9vYg==", back, 3) == -1,
|
|
"four bytes were not read into four, or were into three");
|
|
CHECK(decoded("Zm9vYmE=", "fooba", 5) && server_base64_decode("Zm9vYmE=", back, 4) == -1,
|
|
"five bytes were not read into five, or were into four");
|
|
CHECK(back[4] == '#', "bytes were written past the most");
|
|
return 0;
|
|
}
|
|
fprintf(stderr, "unknown case: %s\n", case_name);
|
|
return 2;
|
|
}
|