/* Copyright information is at the end of the file. */ #ifdef WIN32 #include #else #include #endif #include #include #include #include #include #include "casprintf.h" #include "xmlrpc_config.h" #include "xmlrpc-c/base.h" #include "xmlrpc-c/server.h" #include "bool.h" #include "test.h" #include "value.h" #include "serialize.h" #include "parse_xml.h" #include "cgi.h" #include "xml_data.h" #include "client.h" #include "abyss.h" #include "server_abyss.h" #include "method_registry.h" /*========================================================================= ** Test Harness **========================================================================= ** This is a super light-weight test harness. It's vaguely inspired by ** Kent Beck's book on eXtreme Programming (XP)--the output is succinct, ** new tests can be coded quickly, and the whole thing runs in a few ** second's time. ** ** To run the tests, type './rpctest'. ** To check for memory leaks, install RedHat's 'memprof' utility, and ** type 'memprof rpctest'. ** ** If you add new tests to this file, please deallocate any data ** structures you use in the appropriate fashion. This allows us to test ** various destructor code for memory leaks. */ int total_tests = 0; int total_failures = 0; bool const runningUnderWindows = #ifdef WIN32 true; #else false; #endif /*========================================================================= ** Test Data **========================================================================= ** Some common test data which need to be allocated at a fixed address, ** or which are inconvenient to allocate inline. */ static char* test_string_1 = "foo"; static char* test_string_2 = "bar"; static int test_int_array_1[5] = {1, 2, 3, 4, 5}; static int test_int_array_2[3] = {6, 7, 8}; static int test_int_array_3[8] = {1, 2, 3, 4, 5, 6, 7, 8}; /*========================================================================= ** Test Suites **========================================================================= */ static void test_env(void) { xmlrpc_env env, env2; /* Test xmlrpc_env_init. */ xmlrpc_env_init(&env); TEST(!env.fault_occurred); TEST(env.fault_code == 0); TEST(env.fault_string == NULL); /* Test xmlrpc_set_fault. */ xmlrpc_env_set_fault(&env, 1, test_string_1); TEST(env.fault_occurred); TEST(env.fault_code == 1); TEST(env.fault_string != test_string_1); TEST(strcmp(env.fault_string, test_string_1) == 0); /* Change an existing fault. */ xmlrpc_env_set_fault(&env, 2, test_string_2); TEST(env.fault_occurred); TEST(env.fault_code == 2); TEST(strcmp(env.fault_string, test_string_2) == 0); /* Set a fault with a format string. */ xmlrpc_env_set_fault_formatted(&env, 3, "a%s%d", "bar", 9); TEST(env.fault_occurred); TEST(env.fault_code == 3); TEST(strcmp(env.fault_string, "abar9") == 0); /* Test cleanup code (with help from memprof). */ xmlrpc_env_clean(&env); /* Test cleanup code on in absence of xmlrpc_env_set_fault. */ xmlrpc_env_init(&env2); xmlrpc_env_clean(&env2); } static void test_mem_block (void) { xmlrpc_env env; xmlrpc_mem_block* block; xmlrpc_mem_block* typed_heap_block; xmlrpc_mem_block typed_auto_block; void** typed_contents; xmlrpc_env_init(&env); /* Allocate a zero-size block. */ block = xmlrpc_mem_block_new(&env, 0); TEST_NO_FAULT(&env); TEST(block != NULL); TEST(xmlrpc_mem_block_size(block) == 0); /* Grow the block a little bit. */ xmlrpc_mem_block_resize(&env, block, strlen(test_string_1) + 1); TEST_NO_FAULT(&env); TEST(xmlrpc_mem_block_size(block) == strlen(test_string_1) + 1); /* Insert a string into the block, and resize it by large amount. ** We want to cause a reallocation and copy of the block contents. */ strcpy(xmlrpc_mem_block_contents(block), test_string_1); xmlrpc_mem_block_resize(&env, block, 10000); TEST_NO_FAULT(&env); TEST(xmlrpc_mem_block_size(block) == 10000); TEST(strcmp(xmlrpc_mem_block_contents(block), test_string_1) == 0); /* Test cleanup code (with help from memprof). */ xmlrpc_mem_block_free(block); /* Allocate a bigger block. */ block = xmlrpc_mem_block_new(&env, 128); TEST_NO_FAULT(&env); TEST(block != NULL); TEST(xmlrpc_mem_block_size(block) == 128); /* Test cleanup code (with help from memprof). */ xmlrpc_mem_block_free(block); /* Allocate a "typed" memory block. */ typed_heap_block = XMLRPC_TYPED_MEM_BLOCK_NEW(void*, &env, 20); TEST_NO_FAULT(&env); TEST(typed_heap_block != NULL); TEST(XMLRPC_TYPED_MEM_BLOCK_SIZE(void*, typed_heap_block) == 20); typed_contents = XMLRPC_TYPED_MEM_BLOCK_CONTENTS(void*, typed_heap_block); TEST(typed_contents != NULL); /* Resize a typed memory block. */ XMLRPC_TYPED_MEM_BLOCK_RESIZE(void*, &env, typed_heap_block, 100); TEST_NO_FAULT(&env); TEST(XMLRPC_TYPED_MEM_BLOCK_SIZE(void*, typed_heap_block) == 100); /* Test cleanup code (with help from memprof). */ XMLRPC_TYPED_MEM_BLOCK_FREE(void*, typed_heap_block); /* Test _INIT and _CLEAN for stack-based memory blocks. */ XMLRPC_TYPED_MEM_BLOCK_INIT(void*, &env, &typed_auto_block, 30); TEST(XMLRPC_TYPED_MEM_BLOCK_SIZE(void*, &typed_auto_block) == 30); XMLRPC_TYPED_MEM_BLOCK_CLEAN(void*, &typed_auto_block); /* Test xmlrpc_mem_block_append. */ block = XMLRPC_TYPED_MEM_BLOCK_NEW(int, &env, 5); TEST_NO_FAULT(&env); memcpy(XMLRPC_TYPED_MEM_BLOCK_CONTENTS(int, block), test_int_array_1, sizeof(test_int_array_1)); XMLRPC_TYPED_MEM_BLOCK_APPEND(int, &env, block, test_int_array_2, 3); TEST(XMLRPC_TYPED_MEM_BLOCK_SIZE(int, block) == 8); TEST(memcmp(XMLRPC_TYPED_MEM_BLOCK_CONTENTS(int, block), test_int_array_3, sizeof(test_int_array_3)) == 0); XMLRPC_TYPED_MEM_BLOCK_FREE(int, block); xmlrpc_env_clean(&env); } static char *(base64_triplets[]) = { "", "", "\r\n", "a", "YQ==", "YQ==\r\n", "aa", "YWE=", "YWE=\r\n", "aaa", "YWFh", "YWFh\r\n", "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWmFiY" "2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVo=", "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWmFiY" "2Rl\r\n" "ZmdoaWprbG1ub3BxcnN0dXZ3eHl6QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVo=\r\n", NULL}; static void test_base64_conversion(void) { xmlrpc_env env; char ** triplet; xmlrpc_env_init(&env); for (triplet = base64_triplets; *triplet != NULL; triplet += 3) { char * bin_data; char * nocrlf_ascii_data; char * ascii_data; xmlrpc_mem_block * output; bin_data = *triplet; nocrlf_ascii_data = *(triplet + 1); ascii_data = *(triplet + 2); /* Test our encoding routine. */ output = xmlrpc_base64_encode(&env, (unsigned char*) bin_data, strlen(bin_data)); TEST_NO_FAULT(&env); TEST(output != NULL); TEST(xmlrpc_mem_block_size(output) == strlen(ascii_data)); TEST(memcmp(xmlrpc_mem_block_contents(output), ascii_data, strlen(ascii_data)) == 0); xmlrpc_mem_block_free(output); /* Test our newline-free encoding routine. */ output = xmlrpc_base64_encode_without_newlines(&env, (unsigned char*) bin_data, strlen(bin_data)); TEST_NO_FAULT(&env); TEST(output != NULL); TEST(xmlrpc_mem_block_size(output) == strlen(nocrlf_ascii_data)); TEST(memcmp(xmlrpc_mem_block_contents(output), nocrlf_ascii_data, strlen(nocrlf_ascii_data)) == 0); xmlrpc_mem_block_free(output); /* Test our decoding routine. */ output = xmlrpc_base64_decode(&env, ascii_data, strlen(ascii_data)); TEST_NO_FAULT(&env); TEST(output != NULL); TEST(xmlrpc_mem_block_size(output) == strlen(bin_data)); TEST(memcmp(xmlrpc_mem_block_contents(output), bin_data, strlen(bin_data)) == 0); xmlrpc_mem_block_free(output); } /* Now for something broken... */ { xmlrpc_env env2; xmlrpc_mem_block * output; xmlrpc_env_init(&env2); output = xmlrpc_base64_decode(&env2, "====", 4); TEST(output == NULL); TEST_FAULT(&env2, XMLRPC_PARSE_ERROR); xmlrpc_env_clean(&env2); } /* Now for something broken in a really sneaky way... */ { xmlrpc_env env2; xmlrpc_mem_block * output; xmlrpc_env_init(&env2); output = xmlrpc_base64_decode(&env2, "a==", 4); TEST(output == NULL); TEST_FAULT(&env2, XMLRPC_PARSE_ERROR); xmlrpc_env_clean(&env2); } xmlrpc_env_clean(&env); } static void test_bounds_checks (void) { xmlrpc_env env; xmlrpc_value *array; int i1, i2, i3, i4; /* Get an array to work with. */ xmlrpc_env_init(&env); array = xmlrpc_build_value(&env, "(iii)", 100, 200, 300); TEST_NO_FAULT(&env); xmlrpc_env_clean(&env); /* Test xmlrpc_decompose_value with too few values. */ xmlrpc_env_init(&env); xmlrpc_decompose_value(&env, array, "(iiii)", &i1, &i2, &i3, &i4); TEST_FAULT(&env, XMLRPC_INDEX_ERROR); xmlrpc_env_clean(&env); /* Test xmlrpc_decompose_value with too many values. */ xmlrpc_env_init(&env); xmlrpc_decompose_value(&env, array, "(ii)", &i1, &i2, &i3, &i4); TEST_FAULT(&env, XMLRPC_INDEX_ERROR); xmlrpc_env_clean(&env); /* Dispose of our array. */ xmlrpc_DECREF(array); } static void test_nesting_limit (void) { xmlrpc_env env; xmlrpc_value *val; xmlrpc_env_init(&env); /* Test with an adequate limit for a result value which is an array which contains an element which is a struct, whose values are simple: 3. */ xmlrpc_limit_set(XMLRPC_NESTING_LIMIT_ID, 3); val = xmlrpc_parse_response(&env, good_response_xml, strlen(good_response_xml)); TEST_NO_FAULT(&env); TEST(val != NULL); xmlrpc_DECREF(val); /* Test with an inadequate limit. */ xmlrpc_limit_set(XMLRPC_NESTING_LIMIT_ID, 2); val = xmlrpc_parse_response(&env, good_response_xml, strlen(good_response_xml)); TEST_FAULT(&env, XMLRPC_PARSE_ERROR); /* BREAKME - Will change. */ TEST(val == NULL); /* Reset the default limit. */ xmlrpc_limit_set(XMLRPC_NESTING_LIMIT_ID, XMLRPC_NESTING_LIMIT_DEFAULT); TEST(xmlrpc_limit_get(XMLRPC_NESTING_LIMIT_ID) == XMLRPC_NESTING_LIMIT_DEFAULT); xmlrpc_env_clean(&env); } static void test_xml_size_limit(void) { xmlrpc_env env; const char * methodName; xmlrpc_value * paramsP; /* NOTE - This test suite only verifies the last-ditch size-checking code. There should also be matching code in all server (and preferably all client) modules as well. */ /* Set our XML size limit to something ridiculous. */ xmlrpc_limit_set(XMLRPC_XML_SIZE_LIMIT_ID, 6); /* Attempt to parse a call. */ xmlrpc_env_init(&env); xmlrpc_parse_call(&env, serialized_call, strlen(serialized_call), &methodName, ¶msP); TEST_FAULT(&env, XMLRPC_LIMIT_EXCEEDED_ERROR); xmlrpc_env_clean(&env); { xmlrpc_value * resultP; int faultCode; const char * faultString; /* Attempt to parse a response. */ xmlrpc_env_init(&env); xmlrpc_parse_response2(&env, good_response_xml, strlen(good_response_xml), &resultP, &faultCode, &faultString); TEST_FAULT(&env, XMLRPC_LIMIT_EXCEEDED_ERROR); xmlrpc_env_clean(&env); } /* Reset the default limit. */ xmlrpc_limit_set(XMLRPC_XML_SIZE_LIMIT_ID, XMLRPC_XML_SIZE_LIMIT_DEFAULT); } /*========================================================================= ** test_sample_files **========================================================================= ** Read in a bunch of sample test files and make sure we get plausible ** results. ** ** We use these files to test strange-but-legal encodings, illegal-but- ** allowed-by-Xmlrpc-c encodings, etc. */ #ifdef WIN32 /* usually compiled in 'Windows' folder */ #define TESTDATA_DIR ".." DIRECTORY_SEPARATOR "bin" DIRECTORY_SEPARATOR "data" #else #define TESTDATA_DIR "data" #endif static const char * goodRequests[] = { TESTDATA_DIR DIRECTORY_SEPARATOR "req_out_of_order.xml", TESTDATA_DIR DIRECTORY_SEPARATOR "req_no_params.xml", TESTDATA_DIR DIRECTORY_SEPARATOR "req_value_name.xml", NULL }; #define MAX_SAMPLE_FILE_LEN (16 * 1024) static void reportFileOpenError(const char * const path, int const openErrno) { if (runningUnderWindows) { char cwdname[1024]; char * succeeded; succeeded = getcwd(cwdname, sizeof(cwdname)); if (succeeded) fprintf(stderr, "Running in current work directory '%s'\n", cwdname); } fprintf(stderr, "Could not open file '%s'. errno=%d (%s)\n", path, openErrno, strerror(openErrno)); } static void readFile(const char * const path, const char ** const outDataP, size_t * const outSizeP) { static char fileBuff[MAX_SAMPLE_FILE_LEN]; FILE * fileP; size_t bytesRead; fileP = fopen(path, "r"); if (fileP == NULL) { /* Since this error is fairly likely to happen, give an informative error message... */ reportFileOpenError(path, errno); exit(1); } /* Read in one buffer full of data, and make sure that everything fit. (We perform a lazy error/no-eof/zero-length-file test using 'bytesRead'.) */ bytesRead = fread(fileBuff, sizeof(char), MAX_SAMPLE_FILE_LEN, fileP); TEST(0 < bytesRead && bytesRead < MAX_SAMPLE_FILE_LEN); fclose(fileP); *outDataP = fileBuff; *outSizeP = bytesRead; } static void testSampleFiles(void) { xmlrpc_env env; const char ** pathP; xmlrpc_env_init(&env); /* Test our good requests. */ for (pathP = goodRequests; *pathP != NULL; ++pathP) { const char * const path = *pathP; const char * data; size_t dataLen; const char * methodName; xmlrpc_value * params; readFile(path, &data, &dataLen); xmlrpc_parse_call(&env, data, dataLen, &methodName, ¶ms); TEST_NO_FAULT(&env); strfree(methodName); xmlrpc_DECREF(params); } xmlrpc_env_clean(&env); } /*========================================================================= ** test_utf8_coding **========================================================================= ** We need to test our UTF-8 decoder thoroughly. Most of these test ** cases are taken from the UTF-8-test.txt file by Markus Kuhn ** : ** http://www.cl.cam.ac.uk/~mgk25/ucs/examples/UTF-8-test.txt */ #if HAVE_UNICODE_WCHAR typedef struct { char *utf8; wchar_t wcs[16]; } utf8_and_wcs; static utf8_and_wcs good_utf8[] = { /* Greek 'kosme'. */ {"\316\272\341\275\271\317\203\316\274\316\265", {0x03BA, 0x1F79, 0x03C3, 0x03BC, 0x03B5, 0}}, /* First sequences of a given length. */ /* '\000' is not a legal C string. */ {"\302\200", {0x0080, 0}}, {"\340\240\200", {0x0800, 0}}, /* Last sequences of a given length. */ {"\177", {0x007F, 0}}, {"\337\277", {0x07FF, 0}}, /* 0xFFFF is not a legal Unicode character. */ /* Other boundry conditions. */ {"\001", {0x0001, 0}}, {"\355\237\277", {0xD7FF, 0}}, {"\356\200\200", {0xE000, 0}}, {"\357\277\275", {0xFFFD, 0}}, /* Other random test cases. */ {"", {0}}, {"abc", {0x0061, 0x0062, 0x0063, 0}}, {"[\302\251]", {0x005B, 0x00A9, 0x005D, 0}}, {NULL, {0}} }; static char *(bad_utf8[]) = { /* Continuation bytes. */ "\200", "\277", /* Lonely start characters. */ "\300", "\300x", "\300xx", "\340", "\340x", "\340xx", "\340xxx", /* Last byte missing. */ "\340\200", "\340\200x", "\340\200xx", "\357\277", "\357\277x", "\357\277xx", /* Illegal bytes. */ "\376", "\377", /* Overlong '/'. */ "\300\257", "\340\200\257", /* Overlong ASCII NUL. */ "\300\200", "\340\200\200", /* Maximum overlong sequences. */ "\301\277", "\340\237\277", /* Illegal code positions. */ "\357\277\276", /* U+FFFE */ "\357\277\277", /* U+FFFF */ /* UTF-16 surrogates (unpaired and paired). */ "\355\240\200", "\355\277\277", "\355\240\200\355\260\200", "\355\257\277\355\277\277", /* Valid UCS-4 characters (we don't handle these yet). ** On systems with UCS-4 or UTF-16 wchar_t values, we ** may eventually handle these in some fashion. */ "\360\220\200\200", "\370\210\200\200\200", "\374\204\200\200\200\200", NULL }; #endif /* HAVE_UNICODE_WCHAR */ /* This routine is missing on certain platforms. This implementation ** *appears* to be correct. */ #if 0 #ifndef HAVE_WCSNCMP int wcsncmp(wchar_t *wcs1, wchar_t* wcs2, size_t len) { size_t i; /* XXX - 'unsigned long' should be 'uwchar_t'. */ unsigned long c1, c2; for (i=0; i < len; i++) { c1 = wcs1[i]; c2 = wcs2[i]; /* This clever comparison borrowed from the GNU C Library. */ if (c1 == 0 || c1 != c2) return c1 - c2; } return 0; } #endif /* HAVE_WCSNCMP */ #endif static void test_utf8_coding(void) { #if HAVE_UNICODE_WCHAR xmlrpc_env env, env2; utf8_and_wcs *good_data; char **bad_data; char *utf8; wchar_t *wcs; xmlrpc_mem_block *output; xmlrpc_env_init(&env); /* Test each of our valid UTF-8 sequences. */ for (good_data = good_utf8; good_data->utf8 != NULL; good_data++) { utf8 = good_data->utf8; wcs = good_data->wcs; /* Attempt to validate the UTF-8 string. */ xmlrpc_validate_utf8(&env, utf8, strlen(utf8)); TEST_NO_FAULT(&env); /* Attempt to decode the UTF-8 string. */ output = xmlrpc_utf8_to_wcs(&env, utf8, strlen(utf8)); TEST_NO_FAULT(&env); TEST(output != NULL); TEST(wcslen(wcs) == XMLRPC_TYPED_MEM_BLOCK_SIZE(wchar_t, output)); TEST(0 == wcsncmp(wcs, XMLRPC_TYPED_MEM_BLOCK_CONTENTS(wchar_t, output), wcslen(wcs))); xmlrpc_mem_block_free(output); /* Test the UTF-8 encoder, too. */ output = xmlrpc_wcs_to_utf8(&env, wcs, wcslen(wcs)); TEST_NO_FAULT(&env); TEST(output != NULL); TEST(strlen(utf8) == XMLRPC_TYPED_MEM_BLOCK_SIZE(char, output)); TEST(0 == strncmp(utf8, XMLRPC_TYPED_MEM_BLOCK_CONTENTS(char, output), strlen(utf8))); xmlrpc_mem_block_free(output); } /* Test each of our illegal UTF-8 sequences. */ for (bad_data = bad_utf8; *bad_data != NULL; bad_data++) { utf8 = *bad_data; /* Attempt to validate the UTF-8 string. */ xmlrpc_env_init(&env2); xmlrpc_validate_utf8(&env2, utf8, strlen(utf8)); TEST_FAULT(&env2, XMLRPC_INVALID_UTF8_ERROR); /* printf("Fault: %s\n", env2.fault_string); --Hand-checked */ xmlrpc_env_clean(&env2); /* Attempt to decode the UTF-8 string. */ xmlrpc_env_init(&env2); output = xmlrpc_utf8_to_wcs(&env2, utf8, strlen(utf8)); TEST_FAULT(&env2, XMLRPC_INVALID_UTF8_ERROR); TEST(output == NULL); xmlrpc_env_clean(&env2); } xmlrpc_env_clean(&env); #endif /* HAVE_UNICODE_WCHAR */ } static void test_server_cgi_maybe(void) { #ifndef WIN32 test_server_cgi(); #endif } static void test_client_maybe(void) { #ifndef WIN32 /* Must get Windows Curl transport working for this to work */ test_client(); #endif } int main(int argc, char ** argv ATTR_UNUSED) { int retval; if (argc-1 > 0) { fprintf(stderr, "There are no arguments.\n"); retval = 1; } else { test_env(); test_mem_block(); test_base64_conversion(); printf("\n"); test_value(); test_bounds_checks(); printf("\n"); test_serialize(); test_parse_xml(); test_method_registry(); test_nesting_limit(); test_xml_size_limit(); testSampleFiles(); printf("\n"); test_server_cgi_maybe(); test_abyss(); test_server_abyss(); test_utf8_coding(); printf("\n"); test_client_maybe(); printf("\n"); /* Summarize our test run. */ printf("Ran %d tests, %d failed, %.1f%% passed\n", total_tests, total_failures, 100.0 - (100.0 * total_failures) / total_tests); /* Print the final result. */ if (total_failures == 0) { printf("OK\n"); retval = 0; } else { retval = 1; printf("FAILED\n"); } } return retval; } /* Copyright (C) 2001 by First Peer, Inc. 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. ** 3. The name of the author may not be used to endorse or promote products ** derived from this software without specific prior written permission. ** ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. */