#define AF_INET6 (AF_INET + 1)
#endif
-/* Internal representation of CURLU. Point to URL-encoded strings. */
-struct Curl_URL {
- char *scheme;
- char *user;
- char *password;
- char *options; /* IMAP only? */
- char *host;
- char *zoneid; /* for numerical IPv6 addresses */
- char *port;
- char *path;
- char *query;
- char *fragment;
- unsigned short portnum; /* the numerical version (if 'port' is set) */
- BIT(query_present); /* to support blank */
- BIT(fragment_present); /* to support blank */
- BIT(guessed_scheme); /* when a URL without scheme is parsed */
-};
-
#define DEFAULT_SCHEME "https"
static void free_urlhandle(struct Curl_URL *u)
/* urlencode_str() writes data into an output dynbuf and URL-encodes the
* spaces in the source URL accordingly.
*
+ * This function re-encodes the string, meaning that it leaves already encoded
+ * bytes as-is and works by encoding only what *has* to be encoded - unless it
+ * has to uppercase the hex to normalize.
+ *
+ * Illegal percent-encoding sequences are left as-is.
+ *
* URL encoding should be skipped for hostnames, otherwise IDN resolution
* will fail.
+ *
+ * @unittest: 1675
*/
-static CURLUcode urlencode_str(struct dynbuf *o, const char *url,
- size_t len, bool relative,
- bool query)
+UNITTEST CURLUcode urlencode_str(struct dynbuf *o, const char *url,
+ size_t len, bool relative,
+ bool query);
+UNITTEST CURLUcode urlencode_str(struct dynbuf *o, const char *url,
+ size_t len, bool relative,
+ bool query)
{
/* we must add this with whitespace-replacing */
- bool left = !query;
const unsigned char *iptr;
const unsigned char *host_sep = (const unsigned char *)url;
CURLcode result = CURLE_OK;
for(iptr = host_sep; len && !result; iptr++, len--) {
if(*iptr == ' ') {
- if(left)
+ if(!query)
result = curlx_dyn_addn(o, "%20", 3);
else
result = curlx_dyn_addn(o, "+", 1);
Curl_hexbyte(&out[1], *iptr);
result = curlx_dyn_addn(o, out, 3);
}
+ else if(*iptr == '%' && ISXDIGIT(iptr[1]) && ISXDIGIT(iptr[2]) &&
+ (ISLOWER(iptr[1]) || ISLOWER(iptr[2]))) {
+ /* uppercase it */
+ unsigned char hex = (unsigned char)((curlx_hexval(iptr[1]) << 4) |
+ curlx_hexval(iptr[2]));
+ unsigned char out[3] = { '%' };
+ Curl_hexbyte(&out[1], hex);
+ result = curlx_dyn_addn(o, out, 3);
+ iptr += 2;
+ len -= 2;
+ }
else {
result = curlx_dyn_addn(o, iptr, 1);
if(*iptr == '?')
- left = FALSE;
+ query = TRUE;
}
}
return CURLUE_OK;
}
-/* this assumes 'hostname' now starts with [ */
-static CURLUcode ipv6_parse(struct Curl_URL *u, char *hostname,
- size_t hlen) /* length of hostname */
+/* This function assumes 'hostname' now starts with [. It trims 'hostname' in
+ * place and it sets u->zoneid if present.
+ *
+ * @unittest: 1675
+ */
+UNITTEST CURLUcode ipv6_parse(struct Curl_URL *u, char *hostname,
+ size_t hlen);
+UNITTEST CURLUcode ipv6_parse(struct Curl_URL *u, char *hostname,
+ size_t hlen) /* length of hostname */
{
size_t len;
DEBUGASSERT(*hostname == '[');
return CURLUE_OK;
}
-static CURLUcode hostname_check(struct Curl_URL *u, char *hostname,
- size_t hlen) /* length of hostname */
+UNITTEST CURLUcode hostname_check(struct Curl_URL *u, char *hostname,
+ size_t hlen);
+UNITTEST CURLUcode hostname_check(struct Curl_URL *u, char *hostname,
+ size_t hlen) /* length of hostname */
{
size_t len;
DEBUGASSERT(hostname);
* integers.
*
* Returns the host type.
+ *
+ * @unittest: 1675
*/
-#define HOST_ERROR (-1) /* out of memory */
-
-#define HOST_NAME 1
-#define HOST_IPV4 2
-#define HOST_IPV6 3
-
-static int ipv4_normalize(struct dynbuf *host)
+UNITTEST int ipv4_normalize(struct dynbuf *host);
+UNITTEST int ipv4_normalize(struct dynbuf *host)
{
bool done = FALSE;
int n = 0;
return result ? 1 : 0; /* success */
}
-static CURLUcode parse_file(const char *url, size_t urllen, CURLU *u,
- struct dynbuf *host, const char **pathp,
- size_t *pathlenp)
+/*
+ * @unittest: 1675
+ */
+UNITTEST CURLUcode parse_file(const char *url, size_t urllen, CURLU *u,
+ struct dynbuf *host, const char **pathp,
+ size_t *pathlenp);
+UNITTEST CURLUcode parse_file(const char *url, size_t urllen, CURLU *u,
+ struct dynbuf *host, const char **pathp,
+ size_t *pathlenp)
{
const char *path;
size_t pathlen;
--- /dev/null
+/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, 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 "urlapi-int.h"
+#include "curlx/dynbuf.h"
+
+static CURLcode test_unit1675(const char *arg)
+{
+ (void)arg;
+ UNITTEST_BEGIN_SIMPLE
+
+ /* Test ipv4_normalize */
+ {
+ struct dynbuf host;
+ int fails = 0;
+ unsigned int i;
+ struct ipv4_test {
+ const char *in;
+ const char *out;
+ };
+ const struct ipv4_test tests[] = {
+ {"0x.0x.0x.0x", NULL}, /* invalid hex */
+ {"0x.0x.0x", NULL}, /* invalid hex */
+ {"0x.0x", NULL}, /* invalid hex */
+ {"0x", NULL}, /* invalid hex */
+ {"0", "0.0.0.0"},
+ {"00", "0.0.0.0"},
+ {"00000000000", "0.0.0.0"},
+ {"127.0.0.1", "127.0.0.1"},
+ {"0177.0.0.1", "127.0.0.1"},
+ {"00177.0.0.1", "127.0.0.1"},
+ {"0x7f.0.0.1", "127.0.0.1"},
+ {"0x07f.0.0.1", "127.0.0.1"},
+ {"1", "0.0.0.1"},
+ {"010", "0.0.0.8"},
+ {"001", "0.0.0.1"},
+ {"127", "0.0.0.127"},
+ {"127.1", "127.0.0.1"},
+ {"127.0.1", "127.0.0.1"},
+ {"1.16777215", "1.255.255.255"},
+ {"1.16777216", NULL}, /* overflow */
+ {"1.1.65535", "1.1.255.255"},
+ {"1.1.65536", NULL}, /* overflow */
+ {"0x7f000001", "127.0.0.1"},
+ {"0x7F000001", "127.0.0.1"},
+ {"0x7g000001", NULL}, /* bad hex */
+ {"2130706433", "127.0.0.1"},
+ {"017700000001", "127.0.0.1"},
+ {"000000000017700000001", "127.0.0.1"},
+ {"192.168.0.1", "192.168.0.1"},
+ {"0300.0250.0000.0001", "192.168.0.1"},
+ {"0xc0.0xa8.0.1", "192.168.0.1"},
+ {"0xc0a80001", "192.168.0.1"},
+ {"3232235521", "192.168.0.1"},
+ {"4294967294", "255.255.255.254"},
+ {"4294967295", "255.255.255.255"},
+ {"037777777777", "255.255.255.255"},
+ {"0xFFFFFFFF", "255.255.255.255"},
+ {"0xFFFFFfff", "255.255.255.255"},
+ {"1.2.3.4.5", NULL}, /* too many parts */
+ {"256.0.0.1", NULL}, /* overflow */
+ {"1.256.0.1", NULL}, /* overflow */
+ {"1.1.256.1", NULL}, /* overflow */
+ {"1.0.0.256", NULL}, /* overflow */
+ {"0x100.0.0.1", NULL}, /* overflow */
+ {"1.0x100.0.1", NULL}, /* overflow */
+ {"1.1.0x100.1", NULL}, /* overflow */
+ {"1.1.1.0x100", NULL}, /* overflow */
+ {"0400.0.0.1", NULL}, /* overflow */
+ {"4.0400.0.1", NULL}, /* overflow */
+ {"4.4.0400.1", NULL}, /* overflow */
+ {"4.4.4.0400", NULL}, /* overflow */
+ {"4294967296", NULL}, /* overflow */
+ {"040000000000", NULL}, /* overflow */
+ {"0x100000000", NULL}, /* overflow */
+ {"1.2.3.-4", NULL}, /* negative */
+ {"1.2.-3.4", NULL}, /* negative */
+ {"1.-2.3.4", NULL}, /* negative */
+ {"-1.2.3.4", NULL}, /* negative */
+ {"-12", NULL}, /* negative */
+ {"-12.1", NULL}, /* negative */
+ {"-12.2.3", NULL}, /* negative */
+ {" 1.2.3.4", NULL}, /* space */
+ {"1. 2.3.4", NULL}, /* space */
+ {"1.2. 3.4", NULL}, /* space */
+ {"1.2.3. 4", NULL}, /* space */
+ };
+
+ curlx_dyn_init(&host, 256);
+ for(i = 0; i < CURL_ARRAYSIZE(tests); i++) {
+ int rc;
+ curlx_dyn_reset(&host);
+ if(curlx_dyn_add(&host, tests[i].in)) {
+ return CURLE_OUT_OF_MEMORY;
+ }
+ rc = ipv4_normalize(&host);
+ if(tests[i].out) {
+ if((rc != HOST_IPV4) ||
+ strcmp(curlx_dyn_ptr(&host), tests[i].out)) {
+ curl_mfprintf(stderr, "ipv4_normalize('%s') failed: "
+ "expected '%s', got '%s'\n",
+ tests[i].in, tests[i].out, curlx_dyn_ptr(&host));
+ fails++;
+ }
+ }
+ else {
+ if(rc == HOST_IPV4) {
+ curl_mfprintf(stderr, "ipv4_normalize('%s') succeeded unexpectedly:"
+ " got '%s'\n",
+ tests[i].in, curlx_dyn_ptr(&host));
+ fails++;
+ }
+ }
+ }
+ curlx_dyn_free(&host);
+ abort_if(fails, "ipv4_normalize tests failed");
+ }
+
+ /* Test urlencode_str */
+ {
+ struct dynbuf out;
+ int fails = 0;
+ unsigned int i;
+ struct urlencode_test {
+ const char *in;
+ bool relative;
+ bool query;
+ const char *out;
+ };
+ const struct urlencode_test tests[] = {
+ {"http://leave\x01/hello\x01world", FALSE, FALSE,
+ "http://leave\x01/hello%01world"},
+ {"http://leave/hello\x01world", FALSE, FALSE,
+ "http://leave/hello%01world"},
+ {"http://le ave/hello\x01world", FALSE, FALSE,
+ "http://le ave/hello%01world"},
+ {"hello\x01world", TRUE, FALSE, "hello%01world"},
+ {"hello\xf0world", TRUE, FALSE, "hello%F0world"},
+ {"hello world", TRUE, FALSE, "hello%20world"},
+ {"hello%20world", TRUE, FALSE, "hello%20world"},
+ {"hello world", TRUE, TRUE, "hello+world"},
+ {"a+b c", TRUE, FALSE, "a+b%20c"},
+ {"a%20b%20c", TRUE, FALSE, "a%20b%20c"},
+ {"a%aab%aac", TRUE, FALSE, "a%AAb%AAc"},
+ {"a%aab%AAc", TRUE, FALSE, "a%AAb%AAc"},
+ {"w%w%x", TRUE, FALSE, "w%w%x"},
+ {"w%wf%xf", TRUE, FALSE, "w%wf%xf"},
+ {"w%fw%fw", TRUE, FALSE, "w%fw%fw"},
+ {"a+b c", TRUE, TRUE, "a+b+c"},
+ {"/foo/bar", TRUE, FALSE, "/foo/bar"},
+ {"/foo/bar", TRUE, TRUE, "/foo/bar"},
+ {"/foo/ bar", TRUE, FALSE, "/foo/%20bar"},
+ {"/foo/ bar", TRUE, TRUE, "/foo/+bar"},
+ {"~-._", TRUE, FALSE, "~-._"},
+ {"~-._", TRUE, TRUE, "~-._"},
+ };
+
+ curlx_dyn_init(&out, 256);
+ for(i = 0; i < CURL_ARRAYSIZE(tests); i++) {
+ CURLUcode uc;
+ curlx_dyn_reset(&out);
+ uc = urlencode_str(&out, tests[i].in, strlen(tests[i].in),
+ tests[i].relative, tests[i].query);
+ if(uc || strcmp(curlx_dyn_ptr(&out), tests[i].out)) {
+ curl_mfprintf(stderr, "urlencode_str('%s', query=%d) failed:"
+ " expected '%s', got '%s'\n",
+ tests[i].in, tests[i].query, tests[i].out,
+ uc ? "error" : curlx_dyn_ptr(&out));
+ fails++;
+ }
+ }
+ curlx_dyn_free(&out);
+ abort_if(fails, "urlencode_str tests failed");
+ }
+
+ /* Test ipv6_parse */
+ {
+ struct Curl_URL u;
+ int fails = 0;
+ unsigned int i;
+ struct ipv6_test {
+ const char *in;
+ const char *out_host;
+ const char *out_zone;
+ };
+ const struct ipv6_test tests[] = {
+ {"[::1]", "[::1]", NULL},
+ {"[fe80::1%eth0]", "[fe80::1]", "eth0"},
+ {"[fe80::1%25eth0]", "[fe80::1]", "eth0"},
+ {"[::1", NULL, NULL}, /* missing bracket */
+ {"[]", NULL, NULL}, /* empty */
+ };
+
+ for(i = 0; i < CURL_ARRAYSIZE(tests); i++) {
+ CURLUcode uc;
+ char hostname[256];
+ memset(&u, 0, sizeof(u));
+ curlx_strcopy(hostname, sizeof(hostname),
+ tests[i].in, strlen(tests[i].in));
+ uc = ipv6_parse(&u, hostname, strlen(hostname));
+ if(tests[i].out_host) {
+ if(uc || strcmp(hostname, tests[i].out_host)) {
+ curl_mfprintf(stderr, "ipv6_parse('%s') host failed:"
+ " expected '%s', got '%s'\n",
+ tests[i].in, tests[i].out_host,
+ uc ? "error" : hostname);
+ fails++;
+ }
+ if(!uc && tests[i].out_zone) {
+ if(!u.zoneid || strcmp(u.zoneid, tests[i].out_zone)) {
+ curl_mfprintf(stderr, "ipv6_parse('%s') zone failed:"
+ " expected '%s', got '%s'\n",
+ tests[i].in, tests[i].out_zone,
+ u.zoneid ? u.zoneid : "(null)");
+ fails++;
+ }
+ }
+ }
+ else {
+ if(!uc) {
+ curl_mfprintf(stderr, "ipv6_parse('%s') succeeded unexpectedly\n",
+ tests[i].in);
+ fails++;
+ }
+ }
+ curlx_free(u.host);
+ curlx_free(u.zoneid);
+ }
+ abort_if(fails, "ipv6_parse tests failed");
+ }
+
+ /* Test parse_file */
+ {
+ CURLU *u;
+ const char *path;
+ size_t pathlen;
+ int fails = 0;
+ unsigned int i;
+ struct file_test {
+ const char *in;
+ const char *out_host;
+ const char *out_path;
+ bool fine;
+ };
+ const struct file_test tests[] = {
+ {"file:///etc/hosts", "", "/etc/hosts", TRUE},
+ {"file://localhost/etc/hosts", "", "/etc/hosts", TRUE},
+ {"file://apple/etc/hosts", "", "/etc/hosts", FALSE},
+#ifdef _WIN32
+ {"file:///c:/windows/system32", "", "c:/windows/system32", TRUE},
+ {"file://localhost/c:/windows/system32", "",
+ "c:/windows/system32", TRUE},
+#endif
+ };
+
+ for(i = 0; i < CURL_ARRAYSIZE(tests); i++) {
+ CURLUcode uc;
+ u = curl_url();
+ if(!u)
+ return CURLE_OUT_OF_MEMORY;
+
+ uc = parse_file(tests[i].in, strlen(tests[i].in), u, &host, &path,
+ &pathlen);
+ if(!tests[i].fine && !uc) {
+ curl_mfprintf(stderr, "Unexpectedly fine for input '%s'\n",
+ tests[i].in);
+ fails++;
+ }
+ else if(tests[i].out_host[0]) {
+ /* expecting a hostname output */
+ if(!curlx_dyn_len(&host) ||
+ strcmp(curlx_dyn_ptr(&host), tests[i].out_host))
+ error = TRUE;
+ }
+ if(tests[i].fine &&
+ (uc ||
+ strncmp(path, tests[i].out_path, pathlen) ||
+ strlen(tests[i].out_path) != pathlen)) {
+ curl_mfprintf(stderr, "parse_file('%s') failed:"
+ " expected host '%s', path '%s'; got host '%s',"
+ " path '%.*s'\n",
+ tests[i].in, tests[i].out_host, tests[i].out_path,
+ uc ? "error" : curlx_dyn_ptr(&host),
+ (int)pathlen, path);
+ fails++;
+ }
+ curl_url_cleanup(u);
+ }
+ abort_if(fails, "parse_file tests failed");
+ }
+
+ UNITTEST_END_SIMPLE
+}