From 5c04882b564e7c8b2c3ea9b535e31a6874ae3206 Mon Sep 17 00:00:00 2001 From: Oussama Date: Sat, 25 Jul 2026 13:52:18 +0100 Subject: [PATCH] tests: add unit tests for slist, escape, parsedate and strerror Signed-off-by: Oussama --- tests/data/test3402 | 19 ++++ tests/data/test3403 | 19 ++++ tests/data/test3404 | 19 ++++ tests/data/test3405 | 19 ++++ tests/data/test3406 | 19 ++++ tests/unit/Makefile.inc | 3 +- tests/unit/unit3402.c | 127 +++++++++++++++++++++++ tests/unit/unit3403.c | 220 +++++++++++++++++++++++++++++++++++++++ tests/unit/unit3404.c | 171 +++++++++++++++++++++++++++++++ tests/unit/unit3405.c | 159 ++++++++++++++++++++++++++++ tests/unit/unit3406.c | 222 ++++++++++++++++++++++++++++++++++++++++ 11 files changed, 996 insertions(+), 1 deletion(-) create mode 100644 tests/data/test3402 create mode 100644 tests/data/test3403 create mode 100644 tests/data/test3404 create mode 100644 tests/data/test3405 create mode 100644 tests/data/test3406 create mode 100644 tests/unit/unit3402.c create mode 100644 tests/unit/unit3403.c create mode 100644 tests/unit/unit3404.c create mode 100644 tests/unit/unit3405.c create mode 100644 tests/unit/unit3406.c diff --git a/tests/data/test3402 b/tests/data/test3402 new file mode 100644 index 0000000000..23fcb62f4d --- /dev/null +++ b/tests/data/test3402 @@ -0,0 +1,19 @@ + + + + +unittest +slist + + + +# Client-side + + +unittest + + +slist unit tests (curl_slist_append, Curl_slist_duplicate, curl_slist_free_all) + + + diff --git a/tests/data/test3403 b/tests/data/test3403 new file mode 100644 index 0000000000..c1b6e71dc8 --- /dev/null +++ b/tests/data/test3403 @@ -0,0 +1,19 @@ + + + + +unittest +escape + + + +# Client-side + + +unittest + + +escape unit tests (curl_easy_escape, curl_easy_unescape, Curl_urldecode, Curl_hexencode, Curl_hexbyte) + + + diff --git a/tests/data/test3404 b/tests/data/test3404 new file mode 100644 index 0000000000..3ab72143c6 --- /dev/null +++ b/tests/data/test3404 @@ -0,0 +1,19 @@ + + + + +unittest +endian + + + +# Client-side + + +unittest + + +curl_endian unit tests (Curl_read16_le, Curl_read32_le, Curl_read16_be) + + + diff --git a/tests/data/test3405 b/tests/data/test3405 new file mode 100644 index 0000000000..a55e2fdce3 --- /dev/null +++ b/tests/data/test3405 @@ -0,0 +1,19 @@ + + + + +unittest +parsedate + + + +# Client-side + + +unittest + + +parsedate unit tests (curl_getdate: RFC 1123, RFC 850, asctime, compact, timezones, error cases) + + + diff --git a/tests/data/test3406 b/tests/data/test3406 new file mode 100644 index 0000000000..310aaecf36 --- /dev/null +++ b/tests/data/test3406 @@ -0,0 +1,19 @@ + + + + +unittest +strerror + + + +# Client-side + + +unittest + + +strerror unit tests (curl_easy_strerror, curl_multi_strerror, curl_share_strerror, curl_url_strerror) + + + diff --git a/tests/unit/Makefile.inc b/tests/unit/Makefile.inc index fd05c070e4..fe042ca05e 100644 --- a/tests/unit/Makefile.inc +++ b/tests/unit/Makefile.inc @@ -51,4 +51,5 @@ TESTS_C = \ unit3211.c unit3212.c unit3213.c unit3214.c unit3216.c unit3219.c \ unit3227.c \ unit3300.c unit3301.c unit3302.c unit3303.c unit3304.c unit3306.c \ - unit3400.c + unit3400.c \ + unit3402.c unit3403.c unit3404.c unit3405.c unit3406.c diff --git a/tests/unit/unit3402.c b/tests/unit/unit3402.c new file mode 100644 index 0000000000..fff015c6d8 --- /dev/null +++ b/tests/unit/unit3402.c @@ -0,0 +1,127 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "unitcheck.h" +#include "slist.h" + +static CURLcode test_unit3402(const char *arg) +{ + UNITTEST_BEGIN_SIMPLE + + struct curl_slist *list = NULL; + struct curl_slist *clone = NULL; + struct curl_slist *item = NULL; + + /* --- curl_slist_append: append to NULL creates a new list --- */ + list = curl_slist_append(NULL, "first"); + abort_unless(list, "curl_slist_append on NULL should allocate a new list"); + fail_unless(strcmp(list->data, "first") == 0, + "first node data should be 'first'"); + fail_unless(!list->next, "single-element list must have next == NULL"); + + /* --- curl_slist_append: append a second element --- */ + list = curl_slist_append(list, "second"); + abort_unless(list, "curl_slist_append should return the list head"); + fail_unless(strcmp(list->data, "first") == 0, + "head data must still be 'first' after second append"); + abort_unless(list->next, "list must have a second node"); + fail_unless(strcmp(list->next->data, "second") == 0, + "second node data should be 'second'"); + fail_unless(!list->next->next, "two-element list tail must have next == NULL"); + + /* --- curl_slist_append: append a third element --- */ + list = curl_slist_append(list, "third"); + abort_unless(list && list->next && list->next->next, + "list must have three nodes after three appends"); + fail_unless(strcmp(list->next->next->data, "third") == 0, + "third node data should be 'third'"); + fail_unless(!list->next->next->next, + "three-element list tail must have next == NULL"); + + /* --- curl_slist_append: data is copied (mutation of original is safe) --- */ + { + char buf[16]; + struct curl_slist *copy_test; + memcpy(buf, "mutable", 8); + copy_test = curl_slist_append(NULL, buf); + abort_unless(copy_test, "append of mutable buffer should succeed"); + memcpy(buf, "XXXXXXX", 7); /* mutate original */ + fail_unless(strcmp(copy_test->data, "mutable") == 0, + "slist must own a copy, not a reference to the original"); + curl_slist_free_all(copy_test); + } + + /* --- Curl_slist_append_nodup: takes ownership of a malloc'd string --- */ + { + char *owned = strdup("owned"); + struct curl_slist *nodup_list; + abort_unless(owned, "strdup must succeed"); + nodup_list = Curl_slist_append_nodup(NULL, owned); + abort_unless(nodup_list, "Curl_slist_append_nodup should succeed"); + fail_unless(nodup_list->data == owned, + "nodup variant must store the exact pointer, not a copy"); + curl_slist_free_all(nodup_list); + } + + /* --- Curl_slist_duplicate: NULL input returns NULL --- */ + clone = Curl_slist_duplicate(NULL); + fail_unless(!clone, "duplicating NULL list should return NULL"); + + /* --- Curl_slist_duplicate: clones all nodes with independent storage --- */ + clone = Curl_slist_duplicate(list); + abort_unless(clone, "Curl_slist_duplicate should return a valid list"); + + /* verify each node matches by value */ + item = clone; + fail_unless(strcmp(item->data, "first") == 0, + "cloned node 1 should be 'first'"); + abort_unless(item->next, "clone must have at least 2 nodes"); + item = item->next; + fail_unless(strcmp(item->data, "second") == 0, + "cloned node 2 should be 'second'"); + abort_unless(item->next, "clone must have 3 nodes"); + item = item->next; + fail_unless(strcmp(item->data, "third") == 0, + "cloned node 3 should be 'third'"); + fail_unless(!item->next, "cloned list must end after 3 nodes"); + + /* verify the clone is independent: mutating original does not affect clone */ + fail_unless(clone->data != list->data, + "clone nodes must use separate storage from the original"); + + /* --- curl_slist_free_all: freeing NULL is a no-op --- */ + curl_slist_free_all(NULL); /* must not crash */ + + /* --- curl_slist_free_all: frees a single-element list --- */ + { + struct curl_slist *single = curl_slist_append(NULL, "only"); + abort_unless(single, "single-element list must be created"); + curl_slist_free_all(single); /* must not crash or leak */ + } + + /* cleanup */ + curl_slist_free_all(clone); + curl_slist_free_all(list); + + UNITTEST_END_SIMPLE +} diff --git a/tests/unit/unit3403.c b/tests/unit/unit3403.c new file mode 100644 index 0000000000..0812746ab1 --- /dev/null +++ b/tests/unit/unit3403.c @@ -0,0 +1,220 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "unitcheck.h" +#include "escape.h" + +static CURLcode test_unit3403(const char *arg) +{ + UNITTEST_BEGIN_SIMPLE + + char *result = NULL; + size_t olen = 0; + CURLcode rc; + + /* ================================================================== + * curl_easy_escape + * ================================================================== */ + + /* unreserved characters must pass through unencoded */ + result = curl_easy_escape(NULL, "azAZ09-._~", 0); + abort_unless(result, "escape of unreserved chars must succeed"); + fail_unless(strcmp(result, "azAZ09-._~") == 0, + "unreserved chars must not be percent-encoded"); + curl_free(result); + + /* space must become %20 */ + result = curl_easy_escape(NULL, " ", 0); + abort_unless(result, "escape of space must succeed"); + fail_unless(strcmp(result, "%20") == 0, "space must encode to %20"); + curl_free(result); + + /* all common special chars get encoded */ + result = curl_easy_escape(NULL, "hello world!", 0); + abort_unless(result, "escape of 'hello world!' must succeed"); + fail_unless(strcmp(result, "hello%20world%21") == 0, + "space->%20 and !->%21"); + curl_free(result); + + /* high-byte value 0xFF */ + result = curl_easy_escape(NULL, "\xff", 1); + abort_unless(result, "escape of 0xFF must succeed"); + fail_unless(strcmp(result, "%FF") == 0, "0xFF must encode to %FF"); + curl_free(result); + + /* empty string returns empty string, not NULL */ + result = curl_easy_escape(NULL, "", 0); + abort_unless(result, "escape of empty string must succeed"); + fail_unless(strcmp(result, "") == 0, "escape of empty string must be empty"); + curl_free(result); + + /* explicit length overrides strlen — encode only 5 chars of a longer string */ + result = curl_easy_escape(NULL, "hello world", 5); + abort_unless(result, "escape with explicit length must succeed"); + fail_unless(strcmp(result, "hello") == 0, + "explicit length=5 must produce only 'hello'"); + curl_free(result); + + /* NULL string must return NULL */ + result = curl_easy_escape(NULL, NULL, 0); + fail_unless(!result, "escape of NULL string must return NULL"); + + /* negative length must return NULL */ + result = curl_easy_escape(NULL, "hello", -1); + fail_unless(!result, "escape with negative length must return NULL"); + + /* ================================================================== + * curl_easy_unescape + * ================================================================== */ + + /* basic percent-decode */ + result = curl_easy_unescape(NULL, "%68%65%6c%6c%6f", 0, NULL); + abort_unless(result, "unescape of %%68%%65%%6c%%6c%%6f must succeed"); + fail_unless(strcmp(result, "hello") == 0, + "%%68%%65%%6c%%6c%%6f must decode to 'hello'"); + curl_free(result); + + /* mixed encoded and plain text */ + result = curl_easy_unescape(NULL, "hello%20world", 0, NULL); + abort_unless(result, "unescape of 'hello%%20world' must succeed"); + fail_unless(strcmp(result, "hello world") == 0, + "hello%%20world must decode to 'hello world'"); + curl_free(result); + + /* output length reported correctly */ + { + int outlen = -1; + result = curl_easy_unescape(NULL, "%41%42%43", 0, &outlen); + abort_unless(result, "unescape of %%41%%42%%43 must succeed"); + fail_unless(strcmp(result, "ABC") == 0, + "%%41%%42%%43 must decode to 'ABC'"); + fail_unless(outlen == 3, "output length must be 3"); + curl_free(result); + } + + /* uppercase hex digits also work */ + result = curl_easy_unescape(NULL, "%2F", 0, NULL); + abort_unless(result, "unescape of %%2F must succeed"); + fail_unless(strcmp(result, "/") == 0, "%%2F must decode to '/'"); + curl_free(result); + + /* a lone % that is not followed by two hex digits passes through */ + result = curl_easy_unescape(NULL, "100%", 0, NULL); + abort_unless(result, "unescape of '100%%' must succeed"); + fail_unless(strcmp(result, "100%") == 0, + "trailing lone %% must be kept as-is"); + curl_free(result); + + /* empty input */ + result = curl_easy_unescape(NULL, "", 0, NULL); + abort_unless(result, "unescape of empty string must succeed"); + fail_unless(strcmp(result, "") == 0, "unescape of empty must be empty"); + curl_free(result); + + /* negative length must return NULL */ + result = curl_easy_unescape(NULL, "hello", -1, NULL); + fail_unless(!result, "unescape with negative length must return NULL"); + + /* ================================================================== + * Curl_urldecode — internal decoder with ctrl-char rejection + * ================================================================== */ + + /* REJECT_NADA: accepts everything including control chars */ + rc = Curl_urldecode("%01%02", 0, &result, &olen, REJECT_NADA); + fail_unless(rc == CURLE_OK, "REJECT_NADA must accept control chars"); + fail_unless(olen == 2, "decoded length must be 2"); + curl_free(result); + + /* REJECT_CTRL: rejects decoded bytes < 0x20 */ + rc = Curl_urldecode("%01", 0, &result, &olen, REJECT_CTRL); + fail_unless(rc == CURLE_URL_MALFORMAT, + "REJECT_CTRL must reject control byte 0x01"); + + /* REJECT_CTRL: allows regular bytes */ + rc = Curl_urldecode("hello%20world", 0, &result, &olen, REJECT_CTRL); + fail_unless(rc == CURLE_OK, + "REJECT_CTRL must allow non-control encoded chars"); + fail_unless(olen == 11, "decoded length must be 11"); + fail_unless(strcmp(result, "hello world") == 0, + "REJECT_CTRL decode of 'hello%%20world' must be 'hello world'"); + curl_free(result); + + /* REJECT_ZERO: rejects a decoded NUL byte */ + rc = Curl_urldecode("%00", 0, &result, &olen, REJECT_ZERO); + fail_unless(rc == CURLE_URL_MALFORMAT, + "REJECT_ZERO must reject encoded NUL byte"); + + /* REJECT_ZERO: allows non-NUL bytes */ + rc = Curl_urldecode("ok", 0, &result, &olen, REJECT_ZERO); + fail_unless(rc == CURLE_OK, "REJECT_ZERO must allow normal bytes"); + fail_unless(olen == 2, "decoded length of 'ok' must be 2"); + curl_free(result); + + /* ================================================================== + * Curl_hexencode + * ================================================================== */ + + { + const unsigned char src[] = {0x00, 0x0f, 0x10, 0xff, 0xab}; + unsigned char out[11]; /* 5 bytes * 2 hex chars + NUL */ + memset(out, 0xcc, sizeof(out)); + Curl_hexencode(src, sizeof(src), out, sizeof(out)); + fail_unless(strcmp((char *)out, "000f10ffab") == 0, + "Curl_hexencode must produce lowercase hex"); + fail_unless(out[10] == '\0', "Curl_hexencode must NUL-terminate"); + } + + /* single byte */ + { + const unsigned char src[] = {0xbe}; + unsigned char out[3]; + Curl_hexencode(src, 1, out, sizeof(out)); + fail_unless(strcmp((char *)out, "be") == 0, + "Curl_hexencode single byte 0xBE must be 'be'"); + } + + /* ================================================================== + * Curl_hexbyte + * ================================================================== */ + + { + unsigned char dest[2]; + Curl_hexbyte(dest, 0x00); + fail_unless(dest[0] == '0' && dest[1] == '0', + "Curl_hexbyte(0x00) must produce '00'"); + + Curl_hexbyte(dest, 0xFF); + fail_unless(dest[0] == 'F' && dest[1] == 'F', + "Curl_hexbyte(0xFF) must produce 'FF' (uppercase)"); + + Curl_hexbyte(dest, 0x1A); + fail_unless(dest[0] == '1' && dest[1] == 'A', + "Curl_hexbyte(0x1A) must produce '1A'"); + + Curl_hexbyte(dest, 0xa3); + fail_unless(dest[0] == 'A' && dest[1] == '3', + "Curl_hexbyte(0xa3) must produce 'A3'"); + } + + UNITTEST_END_SIMPLE +} diff --git a/tests/unit/unit3404.c b/tests/unit/unit3404.c new file mode 100644 index 0000000000..752f5ab0e0 --- /dev/null +++ b/tests/unit/unit3404.c @@ -0,0 +1,171 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "unitcheck.h" +#include "curl_endian.h" + +static CURLcode test_unit3404(const char *arg) +{ + UNITTEST_BEGIN_SIMPLE + + /* ================================================================== + * Curl_read16_le — little-endian 16-bit reader + * ================================================================== */ + + /* 0x0102 stored little-endian: low byte first */ + { + const unsigned char buf[] = {0x02, 0x01}; + fail_unless(Curl_read16_le(buf) == 0x0102, + "Curl_read16_le({0x02,0x01}) must return 0x0102"); + } + + /* zero */ + { + const unsigned char buf[] = {0x00, 0x00}; + fail_unless(Curl_read16_le(buf) == 0x0000, + "Curl_read16_le all-zero must return 0"); + } + + /* max value 0xFFFF */ + { + const unsigned char buf[] = {0xFF, 0xFF}; + fail_unless(Curl_read16_le(buf) == 0xFFFF, + "Curl_read16_le({0xFF,0xFF}) must return 0xFFFF"); + } + + /* byte order check: low nibble in first byte, high nibble in second */ + { + const unsigned char buf[] = {0xCD, 0xAB}; + fail_unless(Curl_read16_le(buf) == 0xABCD, + "Curl_read16_le({0xCD,0xAB}) must return 0xABCD"); + } + + /* 0x0100 — only high byte set */ + { + const unsigned char buf[] = {0x00, 0x01}; + fail_unless(Curl_read16_le(buf) == 0x0100, + "Curl_read16_le({0x00,0x01}) must return 0x0100"); + } + + /* ================================================================== + * Curl_read32_le — little-endian 32-bit reader + * ================================================================== */ + + /* 0x01020304 little-endian: lowest byte first */ + { + const unsigned char buf[] = {0x04, 0x03, 0x02, 0x01}; + fail_unless(Curl_read32_le(buf) == 0x01020304U, + "Curl_read32_le({0x04,0x03,0x02,0x01}) must return 0x01020304"); + } + + /* zero */ + { + const unsigned char buf[] = {0x00, 0x00, 0x00, 0x00}; + fail_unless(Curl_read32_le(buf) == 0x00000000U, + "Curl_read32_le all-zero must return 0"); + } + + /* max value 0xFFFFFFFF */ + { + const unsigned char buf[] = {0xFF, 0xFF, 0xFF, 0xFF}; + fail_unless(Curl_read32_le(buf) == 0xFFFFFFFFU, + "Curl_read32_le all-0xFF must return 0xFFFFFFFF"); + } + + /* only highest byte set: 0x01000000 */ + { + const unsigned char buf[] = {0x00, 0x00, 0x00, 0x01}; + fail_unless(Curl_read32_le(buf) == 0x01000000U, + "Curl_read32_le({0,0,0,1}) must return 0x01000000"); + } + + /* only lowest byte set: 0x00000001 */ + { + const unsigned char buf[] = {0x01, 0x00, 0x00, 0x00}; + fail_unless(Curl_read32_le(buf) == 0x00000001U, + "Curl_read32_le({1,0,0,0}) must return 0x00000001"); + } + + /* known value 0xDEADBEEF little-endian */ + { + const unsigned char buf[] = {0xEF, 0xBE, 0xAD, 0xDE}; + fail_unless(Curl_read32_le(buf) == 0xDEADBEEFU, + "Curl_read32_le(DEADBEEF LE) must return 0xDEADBEEF"); + } + + /* ================================================================== + * Curl_read16_be — big-endian 16-bit reader + * ================================================================== */ + + /* 0x0102 stored big-endian: high byte first */ + { + const unsigned char buf[] = {0x01, 0x02}; + fail_unless(Curl_read16_be(buf) == 0x0102, + "Curl_read16_be({0x01,0x02}) must return 0x0102"); + } + + /* zero */ + { + const unsigned char buf[] = {0x00, 0x00}; + fail_unless(Curl_read16_be(buf) == 0x0000, + "Curl_read16_be all-zero must return 0"); + } + + /* max value 0xFFFF */ + { + const unsigned char buf[] = {0xFF, 0xFF}; + fail_unless(Curl_read16_be(buf) == 0xFFFF, + "Curl_read16_be({0xFF,0xFF}) must return 0xFFFF"); + } + + /* byte order check: high nibble in first byte */ + { + const unsigned char buf[] = {0xAB, 0xCD}; + fail_unless(Curl_read16_be(buf) == 0xABCD, + "Curl_read16_be({0xAB,0xCD}) must return 0xABCD"); + } + + /* 0x0100 big-endian */ + { + const unsigned char buf[] = {0x01, 0x00}; + fail_unless(Curl_read16_be(buf) == 0x0100, + "Curl_read16_be({0x01,0x00}) must return 0x0100"); + } + + /* ================================================================== + * Cross-check: LE and BE must differ for asymmetric inputs + * ================================================================== */ + { + const unsigned char buf[] = {0x12, 0x34}; + unsigned short le = Curl_read16_le(buf); + unsigned short be = Curl_read16_be(buf); + fail_unless(le == 0x3412, + "Curl_read16_le({0x12,0x34}) must return 0x3412"); + fail_unless(be == 0x1234, + "Curl_read16_be({0x12,0x34}) must return 0x1234"); + fail_unless(le != be, + "LE and BE of an asymmetric buffer must differ"); + } + + UNITTEST_END_SIMPLE +} diff --git a/tests/unit/unit3405.c b/tests/unit/unit3405.c new file mode 100644 index 0000000000..70049fb421 --- /dev/null +++ b/tests/unit/unit3405.c @@ -0,0 +1,159 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "unitcheck.h" + +/* curl_getdate is a public API declared in curl/curl.h, already pulled in + via unitcheck.h -> curl_setup.h -> curl.h */ + +static CURLcode test_unit3405(const char *arg) +{ + UNITTEST_BEGIN_SIMPLE + +#ifndef CURL_DISABLE_PARSEDATE + + time_t t; + + /* ================================================================== + * RFC 822 / RFC 1123 format: "Sun, 06 Nov 1994 08:49:37 GMT" + * Expected epoch: 784111777 + * ================================================================== */ + t = curl_getdate("Sun, 06 Nov 1994 08:49:37 GMT", NULL); + fail_unless(t == (time_t)784111777, + "RFC 1123 date must parse to epoch 784111777"); + + /* ================================================================== + * RFC 850 format: "Sunday, 06-Nov-94 08:49:37 GMT" + * ================================================================== */ + t = curl_getdate("Sunday, 06-Nov-94 08:49:37 GMT", NULL); + fail_unless(t == (time_t)784111777, + "RFC 850 date must parse to epoch 784111777"); + + /* ================================================================== + * ANSI C asctime() format: "Sun Nov 6 08:49:37 1994" + * ================================================================== */ + t = curl_getdate("Sun Nov 6 08:49:37 1994", NULL); + fail_unless(t == (time_t)784111777, + "asctime() format must parse to epoch 784111777"); + + /* ================================================================== + * Compact numerical: "19941106 08:49:37 GMT" + * ================================================================== */ + t = curl_getdate("19941106 08:49:37 GMT", NULL); + fail_unless(t == (time_t)784111777, + "compact YYYYMMDD format must parse to epoch 784111777"); + + /* ================================================================== + * Date without time (time defaults to 00:00:00) + * "06 Nov 1994" -> 784080000 + * ================================================================== */ + t = curl_getdate("06 Nov 1994", NULL); + fail_unless(t == (time_t)784080000, + "date without time must default to 00:00:00 UTC"); + + /* ================================================================== + * Epoch itself: "01 Jan 1970 00:00:00 GMT" -> 0 + * ================================================================== */ + t = curl_getdate("01 Jan 1970 00:00:00 GMT", NULL); + fail_unless(t == (time_t)0, + "Unix epoch date must parse to 0"); + + /* ================================================================== + * RFC 822 with numeric timezone offset "+0200" + * "12 Sep 2004 15:05:58 +0200" -> UTC 13:05:58 on 2004-09-12 + * Expected epoch: 1094994358 + * ================================================================== */ + t = curl_getdate("12 Sep 2004 15:05:58 +0200", NULL); + fail_unless(t == (time_t)1094994358, + "date with +0200 offset must be adjusted to UTC"); + + /* ================================================================== + * RFC 822 with negative timezone offset "-0700" + * "12 Sep 2004 15:05:58 -0700" -> UTC 22:05:58 on 2004-09-12 + * Expected epoch: 1095026758 + * ================================================================== */ + t = curl_getdate("12 Sep 2004 15:05:58 -0700", NULL); + fail_unless(t == (time_t)1095026758, + "date with -0700 offset must be adjusted to UTC"); + + /* ================================================================== + * Named timezone: CET = UTC+1 (offset -60 min in curl's tz table) + * "06 Nov 1994 08:49:37 CET" -> subtract 3600s from GMT equivalent + * Expected epoch: 784111777 - 3600 = 784108177 + * ================================================================== */ + t = curl_getdate("06 Nov 1994 08:49:37 CET", NULL); + fail_unless(t == (time_t)784108177, + "CET timezone must subtract 3600s from the UTC value"); + + /* ================================================================== + * Two-digit year >= 70 -> 19xx + * "06-Nov-94 08:49:37 GMT" -> year 1994 + * ================================================================== */ + t = curl_getdate("06-Nov-94 08:49:37 GMT", NULL); + fail_unless(t == (time_t)784111777, + "two-digit year 94 must be treated as 1994"); + + /* ================================================================== + * Two-digit year < 70 -> 20xx + * "06 Nov 04 08:49:37 GMT" -> year 2004 + * Expected epoch: 1099730977 + * ================================================================== */ + t = curl_getdate("06 Nov 04 08:49:37 GMT", NULL); + fail_unless(t == (time_t)1099730977, + "two-digit year 04 must be treated as 2004"); + + /* ================================================================== + * Earliest valid year boundary: year 1583 (Gregorian calendar start). + * Anything before 1583 must fail. + * ================================================================== */ + t = curl_getdate("01 Jan 1582 00:00:00 GMT", NULL); + fail_unless(t == (time_t)-1, + "year 1582 is before Gregorian calendar, must return -1"); + + /* ================================================================== + * Invalid / garbage input must return -1 + * ================================================================== */ + t = curl_getdate("not a date at all", NULL); + fail_unless(t == (time_t)-1, + "garbage input must return -1"); + + t = curl_getdate("", NULL); + fail_unless(t == (time_t)-1, + "empty string must return -1"); + + t = curl_getdate("32 Nov 1994 08:49:37 GMT", NULL); + fail_unless(t == (time_t)-1, + "day=32 is illegal, must return -1"); + + t = curl_getdate("06 Nov 1994 25:00:00 GMT", NULL); + fail_unless(t == (time_t)-1, + "hour=25 is illegal, must return -1"); + + t = curl_getdate("06 Foo 1994 08:49:37 GMT", NULL); + fail_unless(t == (time_t)-1, + "unknown month name must return -1"); + +#endif /* CURL_DISABLE_PARSEDATE */ + + UNITTEST_END_SIMPLE +} diff --git a/tests/unit/unit3406.c b/tests/unit/unit3406.c new file mode 100644 index 0000000000..c130e10b44 --- /dev/null +++ b/tests/unit/unit3406.c @@ -0,0 +1,222 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "unitcheck.h" + +/* All four strerror functions are public API, declared in curl/curl.h which + is pulled in transitively through unitcheck.h -> curl_setup.h */ + +static CURLcode test_unit3406(const char *arg) +{ + UNITTEST_BEGIN_SIMPLE + + const char *s; + + /* ================================================================== + * curl_easy_strerror + * In CURLVERBOSE builds each code maps to a unique, non-empty string. + * In non-verbose builds CURLE_OK -> "No error", anything else -> "Error". + * Both modes must return a non-NULL, non-empty string for every value. + * ================================================================== */ + + /* CURLE_OK always maps to a non-empty "success" string */ + s = curl_easy_strerror(CURLE_OK); + abort_unless(s, "curl_easy_strerror(CURLE_OK) must not return NULL"); + fail_unless(strlen(s) > 0, + "curl_easy_strerror(CURLE_OK) must not be empty"); + + /* A handful of well-known error codes must return non-NULL/non-empty */ + s = curl_easy_strerror(CURLE_UNSUPPORTED_PROTOCOL); + abort_unless(s, "curl_easy_strerror(CURLE_UNSUPPORTED_PROTOCOL) != NULL"); + fail_unless(strlen(s) > 0, + "curl_easy_strerror(CURLE_UNSUPPORTED_PROTOCOL) not empty"); + + s = curl_easy_strerror(CURLE_COULDNT_CONNECT); + abort_unless(s, "curl_easy_strerror(CURLE_COULDNT_CONNECT) != NULL"); + fail_unless(strlen(s) > 0, + "curl_easy_strerror(CURLE_COULDNT_CONNECT) not empty"); + + s = curl_easy_strerror(CURLE_OUT_OF_MEMORY); + abort_unless(s, "curl_easy_strerror(CURLE_OUT_OF_MEMORY) != NULL"); + fail_unless(strlen(s) > 0, + "curl_easy_strerror(CURLE_OUT_OF_MEMORY) not empty"); + + s = curl_easy_strerror(CURLE_OPERATION_TIMEDOUT); + abort_unless(s, "curl_easy_strerror(CURLE_OPERATION_TIMEDOUT) != NULL"); + fail_unless(strlen(s) > 0, + "curl_easy_strerror(CURLE_OPERATION_TIMEDOUT) not empty"); + + s = curl_easy_strerror(CURLE_SSL_CONNECT_ERROR); + abort_unless(s, "curl_easy_strerror(CURLE_SSL_CONNECT_ERROR) != NULL"); + fail_unless(strlen(s) > 0, + "curl_easy_strerror(CURLE_SSL_CONNECT_ERROR) not empty"); + + /* An out-of-range code must still return a non-NULL string, not crash */ + s = curl_easy_strerror((CURLcode)9999); + abort_unless(s, "curl_easy_strerror(9999) must not return NULL"); + fail_unless(strlen(s) > 0, + "curl_easy_strerror(9999) must return a non-empty fallback"); + + /* In CURLVERBOSE builds different codes must produce different strings */ +#ifdef CURLVERBOSE + { + const char *s_ok = curl_easy_strerror(CURLE_OK); + const char *s_oom = curl_easy_strerror(CURLE_OUT_OF_MEMORY); + fail_unless(strcmp(s_ok, s_oom) != 0, + "distinct error codes must map to distinct strings"); + } +#endif + + /* ================================================================== + * curl_multi_strerror + * ================================================================== */ + + s = curl_multi_strerror(CURLM_OK); + abort_unless(s, "curl_multi_strerror(CURLM_OK) must not return NULL"); + fail_unless(strlen(s) > 0, + "curl_multi_strerror(CURLM_OK) must not be empty"); + + s = curl_multi_strerror(CURLM_BAD_HANDLE); + abort_unless(s, "curl_multi_strerror(CURLM_BAD_HANDLE) != NULL"); + fail_unless(strlen(s) > 0, + "curl_multi_strerror(CURLM_BAD_HANDLE) not empty"); + + s = curl_multi_strerror(CURLM_OUT_OF_MEMORY); + abort_unless(s, "curl_multi_strerror(CURLM_OUT_OF_MEMORY) != NULL"); + fail_unless(strlen(s) > 0, + "curl_multi_strerror(CURLM_OUT_OF_MEMORY) not empty"); + + s = curl_multi_strerror(CURLM_INTERNAL_ERROR); + abort_unless(s, "curl_multi_strerror(CURLM_INTERNAL_ERROR) != NULL"); + fail_unless(strlen(s) > 0, + "curl_multi_strerror(CURLM_INTERNAL_ERROR) not empty"); + + /* Out-of-range must not crash */ + s = curl_multi_strerror((CURLMcode)9999); + abort_unless(s, "curl_multi_strerror(9999) must not return NULL"); + fail_unless(strlen(s) > 0, + "curl_multi_strerror(9999) must return a non-empty fallback"); + +#ifdef CURLVERBOSE + { + const char *s_ok = curl_multi_strerror(CURLM_OK); + const char *s_bad = curl_multi_strerror(CURLM_BAD_HANDLE); + fail_unless(strcmp(s_ok, s_bad) != 0, + "CURLM_OK and CURLM_BAD_HANDLE must have distinct strings"); + } +#endif + + /* ================================================================== + * curl_share_strerror + * ================================================================== */ + + s = curl_share_strerror(CURLSHE_OK); + abort_unless(s, "curl_share_strerror(CURLSHE_OK) must not return NULL"); + fail_unless(strlen(s) > 0, + "curl_share_strerror(CURLSHE_OK) must not be empty"); + + s = curl_share_strerror(CURLSHE_BAD_OPTION); + abort_unless(s, "curl_share_strerror(CURLSHE_BAD_OPTION) != NULL"); + fail_unless(strlen(s) > 0, + "curl_share_strerror(CURLSHE_BAD_OPTION) not empty"); + + s = curl_share_strerror(CURLSHE_IN_USE); + abort_unless(s, "curl_share_strerror(CURLSHE_IN_USE) != NULL"); + fail_unless(strlen(s) > 0, + "curl_share_strerror(CURLSHE_IN_USE) not empty"); + + s = curl_share_strerror(CURLSHE_INVALID); + abort_unless(s, "curl_share_strerror(CURLSHE_INVALID) != NULL"); + fail_unless(strlen(s) > 0, + "curl_share_strerror(CURLSHE_INVALID) not empty"); + + s = curl_share_strerror(CURLSHE_NOMEM); + abort_unless(s, "curl_share_strerror(CURLSHE_NOMEM) != NULL"); + fail_unless(strlen(s) > 0, + "curl_share_strerror(CURLSHE_NOMEM) not empty"); + + s = curl_share_strerror(CURLSHE_NOT_BUILT_IN); + abort_unless(s, "curl_share_strerror(CURLSHE_NOT_BUILT_IN) != NULL"); + fail_unless(strlen(s) > 0, + "curl_share_strerror(CURLSHE_NOT_BUILT_IN) not empty"); + +#ifdef CURLVERBOSE + { + const char *s_ok = curl_share_strerror(CURLSHE_OK); + const char *s_err = curl_share_strerror(CURLSHE_IN_USE); + fail_unless(strcmp(s_ok, s_err) != 0, + "CURLSHE_OK and CURLSHE_IN_USE must have distinct strings"); + } +#endif + + /* ================================================================== + * curl_url_strerror + * ================================================================== */ + + s = curl_url_strerror(CURLUE_OK); + abort_unless(s, "curl_url_strerror(CURLUE_OK) must not return NULL"); + fail_unless(strlen(s) > 0, + "curl_url_strerror(CURLUE_OK) must not be empty"); + + s = curl_url_strerror(CURLUE_BAD_HANDLE); + abort_unless(s, "curl_url_strerror(CURLUE_BAD_HANDLE) != NULL"); + fail_unless(strlen(s) > 0, + "curl_url_strerror(CURLUE_BAD_HANDLE) not empty"); + + s = curl_url_strerror(CURLUE_MALFORMED_INPUT); + abort_unless(s, "curl_url_strerror(CURLUE_MALFORMED_INPUT) != NULL"); + fail_unless(strlen(s) > 0, + "curl_url_strerror(CURLUE_MALFORMED_INPUT) not empty"); + + s = curl_url_strerror(CURLUE_NO_SCHEME); + abort_unless(s, "curl_url_strerror(CURLUE_NO_SCHEME) != NULL"); + fail_unless(strlen(s) > 0, + "curl_url_strerror(CURLUE_NO_SCHEME) not empty"); + + s = curl_url_strerror(CURLUE_NO_HOST); + abort_unless(s, "curl_url_strerror(CURLUE_NO_HOST) != NULL"); + fail_unless(strlen(s) > 0, + "curl_url_strerror(CURLUE_NO_HOST) not empty"); + + s = curl_url_strerror(CURLUE_BAD_IPV6); + abort_unless(s, "curl_url_strerror(CURLUE_BAD_IPV6) != NULL"); + fail_unless(strlen(s) > 0, + "curl_url_strerror(CURLUE_BAD_IPV6) not empty"); + + /* Out-of-range must not crash */ + s = curl_url_strerror((CURLUcode)9999); + abort_unless(s, "curl_url_strerror(9999) must not return NULL"); + fail_unless(strlen(s) > 0, + "curl_url_strerror(9999) must return a non-empty fallback"); + +#ifdef CURLVERBOSE + { + const char *s_ok = curl_url_strerror(CURLUE_OK); + const char *s_err = curl_url_strerror(CURLUE_NO_HOST); + fail_unless(strcmp(s_ok, s_err) != 0, + "CURLUE_OK and CURLUE_NO_HOST must have distinct strings"); + } +#endif + + UNITTEST_END_SIMPLE +}