Tue, 22 Oct 2024 23:10:31 +0200
avoid state buffer allocation for JSON with reasonable nesting depth
/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright 2024 Mike Becker, Olaf Wintermann All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include "cx/test.h" #include "util_allocator.h" #include "cx/properties.h" #include "cx/hash_map.h" CX_TEST(test_cx_properties_init) { CxProperties prop; CX_TEST_DO { cxPropertiesInitDefault(&prop); CX_TEST_ASSERT(prop.config.delimiter == '='); CX_TEST_ASSERT(prop.config.comment1 == '#'); CX_TEST_ASSERT(prop.config.comment2 == 0); CX_TEST_ASSERT(prop.config.comment3 == 0); CX_TEST_ASSERT(prop.flags == 0); CX_TEST_ASSERT(prop.text == NULL); CX_TEST_ASSERT(prop.buf == NULL); cxPropertiesDestroy(&prop); } } CX_TEST(test_cx_properties_next) { const char *tests[] = { "name = value\n", "name=value\n", "n=value\n", "name=v\n", "n=v\n", "name = value # comment\n", "#comment\nn=v\n", "# comment1\n# comment2\n\n \n\nname = value\n", " name = value\n", "name = value\n\n" }; const char *keys[] = { "name", "name", "n", "name", "n", "name", "n", "name", "name", "name" }; const char *values[] = { "value", "value", "value", "v", "v", "value", "v", "value", "value", "value" }; CxProperties prop; cxPropertiesInitDefault(&prop); CxPropertiesStatus result; cxstring key; cxstring value; CX_TEST_DO { for (int i = 0; i < 10; i++) { cxPropertiesInput(&prop, tests[i], strlen(tests[i])); CX_TEST_ASSERT(prop.text == tests[i]); CX_TEST_ASSERT(prop.text_size == strlen(tests[i])); CX_TEST_ASSERT(prop.text_pos == 0); result = cxPropertiesNext(&prop, &key, &value); cxstring k = cx_str(keys[i]); cxstring v = cx_str(values[i]); CX_TEST_ASSERT(result == CX_PROPERTIES_NO_ERROR); CX_TEST_ASSERT(0 == cx_strcmp(key, k)); CX_TEST_ASSERT(0 == cx_strcmp(value, v)); result = cxPropertiesNext(&prop, &key, &value); CX_TEST_ASSERT(result == CX_PROPERTIES_NO_DATA); } } cxPropertiesDestroy(&prop); } CX_TEST(test_cx_properties_next_multi) { const char *keys[] = { "a", "b", "c", "uap", "name", "key1", "key2", "key3" }; const char *values[] = { "a value", "b value", "core", "core", "ucx", "value1", "value2", "value3" }; const char *str = "#\n" "# properties\n" "# contains key/value pairs\n" "#\n" "a = a value\n" "b = b value\n" "c = core\n" "\n# test\n" "uap = core\n" "name = ucx\n" "# no = property\n" "key1 = value1\n" "#key1 = wrong value\n" "#key2 = not value 2\n" "key2 = value2\n" "\n\n\n \n key3=value3\n"; CxProperties prop; cxPropertiesInitDefault(&prop); CxPropertiesStatus result; cxstring key; cxstring value; CX_TEST_DO { result = cxPropertiesNext(&prop, &key, &value); CX_TEST_ASSERT(result == CX_PROPERTIES_NULL_INPUT); cxPropertiesInput(&prop, str, strlen(str)); for (int i = 0; i < 8; i++) { result = cxPropertiesNext(&prop, &key, &value); CX_TEST_ASSERT(result == CX_PROPERTIES_NO_ERROR); CX_TEST_ASSERT(!cx_strcmp(key, cx_str(keys[i]))); CX_TEST_ASSERT(!cx_strcmp(value, cx_str(values[i]))); } result = cxPropertiesNext(&prop, &key, &value); CX_TEST_ASSERT(result == CX_PROPERTIES_NO_DATA); } cxPropertiesDestroy(&prop); } CX_TEST(test_cx_properties_next_part) { CxProperties prop; cxPropertiesInitDefault(&prop); CxPropertiesStatus result; cxstring key; cxstring value; const char *str; CX_TEST_DO { str = ""; cxPropertiesFill(&prop, str, strlen(str)); result = cxPropertiesNext(&prop, &key, &value); CX_TEST_ASSERT(result == CX_PROPERTIES_NO_DATA); str = " \n"; cxPropertiesFill(&prop, str, strlen(str)); result = cxPropertiesNext(&prop, &key, &value); CX_TEST_ASSERT(result == CX_PROPERTIES_NO_DATA); str = "name"; cxPropertiesFill(&prop, str, strlen(str)); result = cxPropertiesNext(&prop, &key, &value); CX_TEST_ASSERT(result == CX_PROPERTIES_INCOMPLETE_DATA); str = " "; cxPropertiesFill(&prop, str, strlen(str)); result = cxPropertiesNext(&prop, &key, &value); CX_TEST_ASSERT(result == CX_PROPERTIES_INCOMPLETE_DATA); // call fill twice in a row str = "= "; cxPropertiesFill(&prop, str, strlen(str)); str = "value"; cxPropertiesFill(&prop, str, strlen(str)); result = cxPropertiesNext(&prop, &key, &value); CX_TEST_ASSERT(result == CX_PROPERTIES_INCOMPLETE_DATA); str = "\n"; cxPropertiesFill(&prop, str, strlen(str)); result = cxPropertiesNext(&prop, &key, &value); CX_TEST_ASSERT(result == CX_PROPERTIES_NO_ERROR); CX_TEST_ASSERT(0 == cx_strcmp(key, cx_str("name"))); CX_TEST_ASSERT(0 == cx_strcmp(value, cx_str("value"))); // second round str = "#comment\n"; cxPropertiesFill(&prop, str, strlen(str)); result = cxPropertiesNext(&prop, &key, &value); CX_TEST_ASSERT(result == CX_PROPERTIES_NO_DATA); str = "#comment\nname2 = "; cxPropertiesFill(&prop, str, strlen(str)); result = cxPropertiesNext(&prop, &key, &value); CX_TEST_ASSERT(result == CX_PROPERTIES_INCOMPLETE_DATA); str = "value2\na = b\n"; cxPropertiesFill(&prop, str, strlen(str)); result = cxPropertiesNext(&prop, &key, &value); CX_TEST_ASSERT(result == CX_PROPERTIES_NO_ERROR); CX_TEST_ASSERT(0 == cx_strcmp(key, cx_str("name2"))); CX_TEST_ASSERT(0 == cx_strcmp(value, cx_str("value2"))); result = cxPropertiesNext(&prop, &key, &value); CX_TEST_ASSERT(result == CX_PROPERTIES_NO_ERROR); CX_TEST_ASSERT(0 == cx_strcmp(key, cx_str("a"))); CX_TEST_ASSERT(0 == cx_strcmp(value, cx_str("b"))); str = "# comment\n#\n#\ntests = "; cxPropertiesFill(&prop, str, strlen(str)); result = cxPropertiesNext(&prop, &key, &value); CX_TEST_ASSERT(result == CX_PROPERTIES_INCOMPLETE_DATA); str = "test1 "; cxPropertiesFill(&prop, str, strlen(str)); result = cxPropertiesNext(&prop, &key, &value); CX_TEST_ASSERT(result == CX_PROPERTIES_INCOMPLETE_DATA); str = "test2 test3 test4\n"; cxPropertiesFill(&prop, str, strlen(str)); result = cxPropertiesNext(&prop, &key, &value); CX_TEST_ASSERT(result == CX_PROPERTIES_NO_ERROR); CX_TEST_ASSERT(0 == cx_strcmp(key, cx_str("tests"))); CX_TEST_ASSERT(0 == cx_strcmp(value, cx_str("test1 test2 test3 test4"))); // test if cxPropertiesNext finds a name/value after a comment str = "# just a comment"; cxPropertiesFill(&prop, str, strlen(str)); result = cxPropertiesNext(&prop, &key, &value); CX_TEST_ASSERT(result == CX_PROPERTIES_INCOMPLETE_DATA); str = " in 3"; cxPropertiesFill(&prop, str, strlen(str)); result = cxPropertiesNext(&prop, &key, &value); CX_TEST_ASSERT(result == CX_PROPERTIES_INCOMPLETE_DATA); str = " parts\nx = 1\n"; cxPropertiesFill(&prop, str, strlen(str)); result = cxPropertiesNext(&prop, &key, &value); CX_TEST_ASSERT(result == CX_PROPERTIES_NO_ERROR); CX_TEST_ASSERT(0 == cx_strcmp(key, cx_str("x"))); CX_TEST_ASSERT(0 == cx_strcmp(value, cx_str("1"))); // finally we are done result = cxPropertiesNext(&prop, &key, &value); CX_TEST_ASSERT(result == CX_PROPERTIES_NO_DATA); } cxPropertiesDestroy(&prop); } CX_TEST(test_ucx_properties_next_long_lines) { CxProperties prop; cxPropertiesInitDefault(&prop); CxPropertiesStatus result; cxstring key; cxstring value; size_t key_len = 512; char *long_key = (char*)malloc(key_len); memset(long_key, 'a', 70); memset(long_key + 70, 'b', 242); memset(long_key + 312, 'c', 200); size_t value_len = 2048; char *long_value = (char*)malloc(value_len); memset(long_value, 'x', 1024); memset(long_value+1024, 'y', 1024); CX_TEST_DO { cxPropertiesFill(&prop, long_key, 10); result = cxPropertiesNext(&prop, &key, &value); CX_TEST_ASSERT(result == CX_PROPERTIES_INCOMPLETE_DATA); cxPropertiesFill(&prop, long_key + 10, 202); result = cxPropertiesNext(&prop, &key, &value); CX_TEST_ASSERT(result == CX_PROPERTIES_INCOMPLETE_DATA); cxPropertiesFill(&prop, long_key + 212, 200); result = cxPropertiesNext(&prop, &key, &value); CX_TEST_ASSERT(result == CX_PROPERTIES_INCOMPLETE_DATA); cxPropertiesFill(&prop, long_key + 412, 100); result = cxPropertiesNext(&prop, &key, &value); CX_TEST_ASSERT(result == CX_PROPERTIES_INCOMPLETE_DATA); const char *str = " = "; cxPropertiesFill(&prop, str, strlen(str)); result = cxPropertiesNext(&prop, &key, &value); CX_TEST_ASSERT(result == CX_PROPERTIES_INCOMPLETE_DATA); cxPropertiesFill(&prop, long_value, 512); result = cxPropertiesNext(&prop, &key, &value); CX_TEST_ASSERT(result == CX_PROPERTIES_INCOMPLETE_DATA); cxPropertiesFill(&prop, long_value + 512, 1024); result = cxPropertiesNext(&prop, &key, &value); CX_TEST_ASSERT(result == CX_PROPERTIES_INCOMPLETE_DATA); cxPropertiesFill(&prop, long_value + 1536, 512); result = cxPropertiesNext(&prop, &key, &value); CX_TEST_ASSERT(result == CX_PROPERTIES_INCOMPLETE_DATA); str = "\n#comment\nkey = value\n"; cxPropertiesFill(&prop, str, strlen(str)); result = cxPropertiesNext(&prop, &key, &value); cxstring k = cx_strn(long_key, key_len); cxstring v = cx_strn(long_value, value_len); CX_TEST_ASSERT(result == CX_PROPERTIES_NO_ERROR); CX_TEST_ASSERT(0 == cx_strcmp(key, k)); CX_TEST_ASSERT(0 == cx_strcmp(value, v)); result = cxPropertiesNext(&prop, &key, &value); CX_TEST_ASSERT(result == CX_PROPERTIES_NO_ERROR); CX_TEST_ASSERT(0 == cx_strcmp(key, cx_str("key"))); CX_TEST_ASSERT(0 == cx_strcmp(value, cx_str("value"))); result = cxPropertiesNext(&prop, &key, &value); CX_TEST_ASSERT(result == CX_PROPERTIES_NO_DATA); CX_TEST_ASSERT(prop.buf != NULL); CX_TEST_ASSERT(prop.buf_capacity > 0); CX_TEST_ASSERT(prop.buf_size == 0); cxPropertiesDestroy(&prop); CX_TEST_ASSERT(prop.buf == NULL); CX_TEST_ASSERT(prop.buf_capacity == 0); CX_TEST_ASSERT(prop.buf_size == 0); } free(long_key); free(long_value); } CX_TEST(test_cx_properties_load_string_to_map) { CxTestingAllocator talloc; cx_testing_allocator_init(&talloc); CxAllocator *alloc = &talloc.base; CX_TEST_DO { char buffer[512]; CxProperties prop; cxPropertiesInitDefault(&prop); cxPropertiesUseStack(&prop, buffer, 512); const char *str = "key1 = value1\nkey2 = value2\n\n#comment\n\nkey3 = value3\n"; CxMap *map = cxHashMapCreateSimple(CX_STORE_POINTERS); cxDefineAdvancedDestructor(map, cxFree, alloc); CxPropertiesSink sink = cxPropertiesMapSink(map); sink.data = alloc; // use the testing allocator CxPropertiesSource src = cxPropertiesCstrSource(str); CxPropertiesStatus status = cxPropertiesLoad(&prop, sink, src); CX_TEST_ASSERT(status == CX_PROPERTIES_NO_ERROR); CX_TEST_ASSERT(cxMapSize(map) == 3); char *v1 = cxMapGet(map, "key1"); char *v2 = cxMapGet(map, "key2"); char *v3 = cxMapGet(map, "key3"); CX_TEST_ASSERTM(v1, "value for key1 not found"); CX_TEST_ASSERTM(v2, "value for key2 not found"); CX_TEST_ASSERTM(v3, "value for key3 not found"); CX_TEST_ASSERT(!strcmp(v1, "value1")); CX_TEST_ASSERT(!strcmp(v2, "value2")); CX_TEST_ASSERT(!strcmp(v3, "value3")); // second test cxMapClear(map); str = "\n#comment\n"; src = cxPropertiesCstrnSource(str, strlen(str)); status = cxPropertiesLoad(&prop, sink, src); CX_TEST_ASSERT(status == CX_PROPERTIES_NO_DATA); CX_TEST_ASSERT(cxMapSize(map) == 0); str = "key1 = value1\nsyntax error line\n"; src = cxPropertiesStringSource(cx_str(str)); status = cxPropertiesLoad(&prop, sink, src); CX_TEST_ASSERT(status == CX_PROPERTIES_INVALID_MISSING_DELIMITER); // the successfully read k/v-pair is in the map, nevertheless CX_TEST_ASSERT(cxMapSize(map) == 1); char *v = cxMapGet(map, "key1"); CX_TEST_ASSERT(!strcmp(v, "value1")); cxMapDestroy(map); cxPropertiesDestroy(&prop); CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc)); } cx_testing_allocator_destroy(&talloc); } CX_TEST(test_cx_properties_load_file_to_map) { CxTestingAllocator talloc; cx_testing_allocator_init(&talloc); CxAllocator *alloc = &talloc.base; CX_TEST_DO { FILE *f = tmpfile(); CX_TEST_ASSERTM(f, "test file cannot be opened, test aborted"); fprintf(f, "# properties file\n\nkey1 = value1\nkey2 = value2\n"); fprintf(f, "\n\nkey3 = value3\n\n"); size_t key_len = 512; char *long_key = (char *) malloc(key_len); memset(long_key, 'k', 512); size_t value_len = 2048; char *long_value = (char *) malloc(value_len); memset(long_value, 'v', 2048); fwrite(long_key, 1, key_len, f); fprintf(f, " = "); fwrite(long_value, 1, value_len, f); fprintf(f, " \n"); fprintf(f, "\n\n\n\nlast_key = property value\n"); fflush(f); fseek(f, 0, SEEK_SET); // preparation of test file complete CxMap *map = cxHashMapCreateSimple(CX_STORE_POINTERS); cxDefineAdvancedDestructor(map, cxFree, alloc); CxProperties prop; cxPropertiesInitDefault(&prop); CxPropertiesSink sink = cxPropertiesMapSink(map); sink.data = alloc; // use the testing allocator CxPropertiesSource src = cxPropertiesFileSource(f, 512); CxPropertiesStatus status = cxPropertiesLoad(&prop, sink, src); fclose(f); CX_TEST_ASSERT(status == CX_PROPERTIES_NO_ERROR); CX_TEST_ASSERT(cxMapSize(map) == 5); char *v1 = cxMapGet(map, "key1"); char *v2 = cxMapGet(map, cx_str("key2")); char *v3 = cxMapGet(map, "key3"); char *lv = cxMapGet(map, cx_strn(long_key, key_len)); char *lk = cxMapGet(map, "last_key"); CX_TEST_ASSERTM(v1, "value for key1 not found"); CX_TEST_ASSERTM(v2, "value for key2 not found"); CX_TEST_ASSERTM(v3, "value for key3 not found"); CX_TEST_ASSERTM(lv, "value for long key not found"); CX_TEST_ASSERTM(lk, "value for last_key not found"); CX_TEST_ASSERT(!strcmp(v1, "value1")); CX_TEST_ASSERT(!strcmp(v2, "value2")); CX_TEST_ASSERT(!strcmp(v3, "value3")); cxstring expected = cx_strn(long_value, value_len); cxstring actual = cx_str(lv); CX_TEST_ASSERT(!cx_strcmp(expected, actual)); CX_TEST_ASSERT(!strcmp(lk, "property value")); free(long_key); free(long_value); cxMapDestroy(map); cxPropertiesDestroy(&prop); CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc)); } cx_testing_allocator_destroy(&talloc); } CxTestSuite *cx_test_suite_properties(void) { CxTestSuite *suite = cx_test_suite_new("properties"); cx_test_register(suite, test_cx_properties_init); cx_test_register(suite, test_cx_properties_next); cx_test_register(suite, test_cx_properties_next_multi); cx_test_register(suite, test_cx_properties_next_part); cx_test_register(suite, test_ucx_properties_next_long_lines); cx_test_register(suite, test_cx_properties_load_string_to_map); cx_test_register(suite, test_cx_properties_load_file_to_map); return suite; }