From b3e0ce96afc90087ee994ba3ca5d1f6b64b1b115 Mon Sep 17 00:00:00 2001 From: Garrett D'Amore Date: Mon, 18 Nov 2024 01:17:24 -0800 Subject: [PATCH] URL refactor part 1. This eliminates most (but not all) of the dynamic allocations associated with URL objects. A number of convenience fields on the URL are removed, but we are able to use common buffer for most of the details. --- docs/ref/api/url.md | 7 +- docs/ref/migrate/nng1.md | 13 +- include/nng/nng.h | 8 +- src/core/url.c | 254 +++++++++++++------------- src/core/url_test.c | 64 +++---- src/platform/posix/posix_resolv_gai.c | 9 +- src/platform/windows/win_resolv.c | 10 +- src/sp/transport/socket/sockfd.c | 2 +- src/sp/transport/tcp/tcp_test.c | 13 +- src/sp/transport/tls/tls_tran_test.c | 54 ------ src/sp/transport/udp/udp_tran_test.c | 13 +- src/sp/transport/ws/ws_test.c | 4 +- src/supplemental/http/http_msg.c | 16 +- 13 files changed, 197 insertions(+), 270 deletions(-) diff --git a/docs/ref/api/url.md b/docs/ref/api/url.md index a7fb6f355..db9f3be23 100644 --- a/docs/ref/api/url.md +++ b/docs/ref/api/url.md @@ -14,13 +14,11 @@ that are not part of the IETF standards. typedef struct nng_url { const char *u_scheme; char *u_userinfo; - char *u_host; char *u_hostname; uint16_t u_port; char *u_path; char *u_query; char *u_fragment; - char *u_requri; } nng_url; ``` @@ -29,17 +27,18 @@ typedef struct nng_url { Applications may access individual fields, but must not free or alter them, as the underlying memory is managed by the library. +Additionally applications must not depend on the size of this structure. +Obtain one using `nng_parse_url`. + The fields of an `nng_url` object are as follows: - `u_scheme`: The URL scheme, such as "http" or "inproc". Always lower case. This will never be `NULL`. - `u_userinfo`: This username and password if supplied in the URL string. Will be `NULL` when not present. -- `u_host`: The full host part of the URL, including the port if present (separated by a colon.) - `u_hostname`: The name of the host, and may be the empty string in some cases. - `u_port`: The port. May be zero if irrelevant or not specified. - `u_path`: The path, typically used with HTTP or WebSockets. Will be empty string if not specified. - `u_query`: The query info (typically following `?` in the URL.) Will be `NULL` if not present. - `u_fragment`: This is used for specifying an anchor, the part after `#` in a URL. Will be `NULL` if not present. -- `u_requri`: The full Request-URI. Will be the empty string if not specified. > [!NOTE] > Other fields may also be present, but only those documented here are safe for application use. diff --git a/docs/ref/migrate/nng1.md b/docs/ref/migrate/nng1.md index b7ddea33a..ddad9ff72 100644 --- a/docs/ref/migrate/nng1.md +++ b/docs/ref/migrate/nng1.md @@ -160,11 +160,20 @@ A number of the [statistics][statistic] functions take, or return, `const nng_st of plain `nng_stat *`. The ABI has not changed, but it may be necessary to declare certain methods variables `const` to avoid warnings about misuse of `const`. -## Url Structure Members +## Wildcards Not Valid in URLs + +The use of `*` to act as a wild card meaning all local interface addresses +is removed. The empty string already performs this function, and unlike +`*` is RFC compliant. + +## URL Structure Members The details of [`nng_url`] have changed as follows: - `u_port` is no longer a string, but a `uint16_t` -- `u_scheme` is a const char \* +- `u_scheme` is a `const char *` +- `u_requri` is removed - it can be easily formulated from the other fields. +- `u_host` is removed - use `u_hostname` and `u_port` to construct if needed +- `u_rawurl` is removed - a "cooked" URL can be obtained from the new [`nng_url_sprintf`] function. {{#include ../xref.md}} diff --git a/include/nng/nng.h b/include/nng/nng.h index 984f67d4c..47260b8d8 100644 --- a/include/nng/nng.h +++ b/include/nng/nng.h @@ -1085,14 +1085,16 @@ enum nng_errno_enum { typedef struct nng_url { char *u_rawurl; // never NULL const char *u_scheme; // never NULL - char *u_userinfo; // will be NULL if not specified - char *u_host; // including colon and port + const char *u_userinfo; // will be NULL if not specified char *u_hostname; // name only, will be "" if not specified uint16_t u_port; // port, may be zero for schemes that do not use char *u_path; // path, will be "" if not specified char *u_query; // without '?', will be NULL if not specified char *u_fragment; // without '#', will be NULL if not specified - char *u_requri; // includes query and fragment, "" if not specified + // these members are private + char *u_buffer; + size_t u_bufsz; + char u_static[NNG_MAXADDRLEN]; // Most URLs fit within this } nng_url; // nng_url_parse parses a URL string into a structured form. diff --git a/src/core/url.c b/src/core/url.c index bae5a4911..c5be4f835 100644 --- a/src/core/url.c +++ b/src/core/url.c @@ -8,6 +8,7 @@ // found online at https://opensource.org/licenses/MIT. // +#include "core/defs.h" #include "core/nng_impl.h" #include @@ -16,6 +17,7 @@ #include #include "core/platform.h" +#include "nng/nng.h" #include "url.h" static uint8_t @@ -118,27 +120,19 @@ nni_url_decode(uint8_t *out, const char *in, size_t max_len) } static int -url_canonify_uri(char **outp, const char *in) +url_canonify_uri(char *out) { - char *out; - size_t src, dst, len; + size_t src, dst; uint8_t c; int rv; bool skip; - // We know that the transform is strictly "reducing". - if ((out = nni_strdup(in)) == NULL) { - return (NNG_ENOMEM); - } - len = strlen(out); - // First pass, convert '%xx' for safe characters to unescaped forms. src = dst = 0; while ((c = out[src]) != 0) { if (c == '%') { if ((!isxdigit(out[src + 1])) || (!isxdigit(out[src + 2]))) { - nni_free(out, len); return (NNG_EINVAL); } c = url_hex_val(out[src + 1]); @@ -224,13 +218,10 @@ url_canonify_uri(char **outp, const char *in) // Finally lets make sure that the results are valid UTF-8. // This guards against using UTF-8 redundancy to break security. if ((rv = url_utf8_validate(out)) != 0) { - nni_free(out, len); return (rv); } - *outp = nni_strdup(out); - nni_free(out, len); - return (*outp == NULL ? NNG_ENOMEM : 0); + return (0); } static struct { @@ -340,6 +331,7 @@ nni_url_parse(nni_url **urlp, const char *raw) nni_url *url; size_t len; const char *s; + char *p; char c; int rv; @@ -347,6 +339,7 @@ nni_url_parse(nni_url **urlp, const char *raw) return (NNG_ENOMEM); } + // TODO: remove this when NNG_OPT_URL is gone if ((url->u_rawurl = nni_strdup(raw)) == NULL) { rv = NNG_ENOMEM; goto error; @@ -374,7 +367,23 @@ nni_url_parse(nni_url **urlp, const char *raw) rv = NNG_ENOTSUP; goto error; } - s += len + 3; // strlen("://") + s += len; + + // A little tricky. We copy the "://" here, even though we don't need + // it. This affords us some space for zero bytes between URL components + // if needed + + if (strlen(s) >= sizeof(url->u_static)) { + url->u_buffer = nni_strdup(s); + url->u_bufsz = strlen(s) + 1; + } else { + snprintf(url->u_static, sizeof(url->u_static), "%s", s); + url->u_buffer = url->u_static; + url->u_bufsz = 0; + } + + p = url->u_buffer + strlen("://"); + s = p; // For compatibility reasons, we treat ipc:// and inproc:// paths // specially. These names URLs have a path name (ipc) or arbitrary @@ -390,152 +399,119 @@ nni_url_parse(nni_url **urlp, const char *raw) (strcmp(url->u_scheme, "unix") == 0) || (strcmp(url->u_scheme, "abstract") == 0) || (strcmp(url->u_scheme, "inproc") == 0)) { - if ((url->u_path = nni_strdup(s)) == NULL) { - rv = NNG_ENOMEM; - goto error; - } - *urlp = url; + url->u_path = p; + *urlp = url; return (0); } // Look for host part (including colon). Will be terminated by // a path, or NUL. May also include an "@", separating a user // field. - for (len = 0; (c = s[len]) != '/'; len++) { - if ((c == '\0') || (c == '#') || (c == '?')) { + for (;;) { + c = *p; + if ((c == '\0') || (c == '/') || (c == '#') || (c == '?')) { + *p = '\0'; + memmove(url->u_buffer, s, strlen(s) + 1); + *p = c; break; } - if (c == '@') { - // This is a username. - if (url->u_userinfo != NULL) { // we already have one - rv = NNG_EINVAL; - goto error; - } - if ((url->u_userinfo = nni_alloc(len + 1)) == NULL) { - rv = NNG_ENOMEM; - goto error; - } - memcpy(url->u_userinfo, s, len); - url->u_userinfo[len] = '\0'; - s += len + 1; // skip past user@ ... - len = 0; - } + p++; } - // If the hostname part is just '*', skip over it. (We treat it - // as an empty host for legacy nanomsg compatibility. This may be - // non-RFC compliant, but we're really only interested in parsing - // nanomsg URLs.) - if (((len == 1) && (s[0] == '*')) || - ((len > 1) && (strncmp(s, "*:", 2) == 0))) { - s++; - len--; - } + s = p; + url->u_path = p; - if ((url->u_host = nni_alloc(len + 1)) == NULL) { - rv = NNG_ENOMEM; - goto error; + // shift the host back to the start of the buffer, which gives us + // padding so we don't have to clobber the leading "/" in the path. + url->u_hostname = url->u_buffer; + + char *at; + if ((at = strchr(url->u_hostname, '@')) != NULL) { + url->u_userinfo = url->u_hostname; + *at++ = 0; + url->u_hostname = at; + + // make sure only one '@' appears in the host (only one user + // info is allowed) + if (strchr(url->u_hostname, '@') != NULL) { + rv = NNG_EADDRINVAL; + goto error; + } } + // Copy the host portion, but make it lower case (hostnames are // case insensitive). - for (size_t i = 0; i < len; i++) { - url->u_host[i] = (char) tolower(s[i]); + for (int i = 0; url->u_hostname[i]; i++) { + url->u_hostname[i] = (char) tolower(url->u_hostname[i]); } - url->u_host[len] = '\0'; - s += len; - if ((rv = url_canonify_uri(&url->u_requri, s)) != 0) { + if ((rv = url_canonify_uri(p)) != 0) { goto error; } - s = url->u_requri; - for (len = 0; (c = s[len]) != '\0'; len++) { + while ((c = *p) != '\0') { if ((c == '?') || (c == '#')) { break; } + p++; } - if ((url->u_path = nni_alloc(len + 1)) == NULL) { - rv = NNG_ENOMEM; - goto error; - } - memcpy(url->u_path, s, len); - url->u_path[len] = '\0'; - - s += len; - // Look for query info portion. - if (s[0] == '?') { - s++; - for (len = 0; (c = s[len]) != '\0'; len++) { + if (*p == '?') { + *p++ = '\0'; + url->u_query = p; + while ((c = *p) != '\0') { if (c == '#') { + *p++ = '\0'; + url->u_fragment = p; break; } + p++; } - if ((url->u_query = nni_alloc(len + 1)) == NULL) { - rv = NNG_ENOMEM; - goto error; - } - memcpy(url->u_query, s, len); - url->u_query[len] = '\0'; - s += len; - } - - // Look for fragment. Will always be last, so we just use - // strdup. - if (s[0] == '#') { - if ((url->u_fragment = nni_strdup(s + 1)) == NULL) { - rv = NNG_ENOMEM; - goto error; - } + } else if (c == '#') { + *p++ = '\0'; + url->u_fragment = p; } // Now go back to the host portion, and look for a separate // port We also yank off the "[" part for IPv6 addresses. - s = url->u_host; - if (s[0] == '[') { - s++; - for (len = 0; s[len] != ']'; len++) { - if (s[len] == '\0') { + p = url->u_hostname; + if (*p == '[') { + url->u_hostname++; + p++; + while (*p != ']') { + if (*p++ == '\0') { rv = NNG_EINVAL; goto error; } } - if ((s[len + 1] != ':') && (s[len + 1] != '\0')) { + *p++ = '\0'; + if ((*p != ':') && (*p != '\0')) { rv = NNG_EINVAL; goto error; } } else { - for (len = 0; s[len] != ':'; len++) { - if (s[len] == '\0') { - break; - } + while (*p != ':' && *p != '\0') { + p++; } } + if ((c = *p) == ':') { + *p++ = '\0'; + } // hostname length check - if (len >= 256) { + if (strlen(url->u_hostname) >= 256) { rv = NNG_EADDRINVAL; goto error; } - if ((url->u_hostname = nni_alloc(len + 1)) == NULL) { - rv = NNG_ENOMEM; - goto error; - } - memcpy(url->u_hostname, s, len); - url->u_hostname[len] = '\0'; - s += len; - if (s[0] == ']') { - s++; // skip over ']', only used with IPv6 addresses - } - if (s[0] == ':') { + if (c == ':') { // If a colon was present, but no port value present, then // that is an error. - if (s[1] == '\0') { + if (*p == '\0') { rv = NNG_EINVAL; goto error; } - rv = nni_get_port_by_name(s + 1, &url->u_port); + rv = nni_get_port_by_name(p, &url->u_port); if (rv != 0) { goto error; } @@ -556,13 +532,9 @@ nni_url_free(nni_url *url) { if (url != NULL) { nni_strfree(url->u_rawurl); - nni_strfree(url->u_userinfo); - nni_strfree(url->u_host); - nni_strfree(url->u_hostname); - nni_strfree(url->u_path); - nni_strfree(url->u_query); - nni_strfree(url->u_fragment); - nni_strfree(url->u_requri); + if (url->u_bufsz != 0) { + nni_free(url->u_buffer, url->u_bufsz); + } NNI_FREE_STRUCT(url); } } @@ -585,9 +557,6 @@ nni_url_sprintf(char *str, size_t size, const nni_url *url) if (url->u_port == nni_url_default_port(scheme)) { do_port = false; } - if (strcmp(host, "*") == 0) { - host = ""; - } if (strchr(host, ':') != 0) { hostob = "["; hostcb = "]"; @@ -598,8 +567,12 @@ nni_url_sprintf(char *str, size_t size, const nni_url *url) } else { portstr[0] = 0; } - return (snprintf(str, size, "%s://%s%s%s%s%s", scheme, hostob, host, - hostcb, portstr, url->u_requri != NULL ? url->u_requri : "")); + return (snprintf(str, size, "%s://%s%s%s%s%s%s%s%s%s", scheme, hostob, + host, hostcb, portstr, url->u_path, + url->u_query != NULL ? "?" : "", + url->u_query != NULL ? url->u_query : "", + url->u_fragment != NULL ? "#" : "", + url->u_fragment != NULL ? url->u_fragment : "")); } int @@ -641,17 +614,38 @@ nni_url_clone(nni_url **dstp, const nni_url *src) if ((dst = NNI_ALLOC_STRUCT(dst)) == NULL) { return (NNG_ENOMEM); } - if (URL_COPYSTR(dst->u_rawurl, src->u_rawurl) || - URL_COPYSTR(dst->u_userinfo, src->u_userinfo) || - URL_COPYSTR(dst->u_host, src->u_host) || - URL_COPYSTR(dst->u_hostname, src->u_hostname) || - URL_COPYSTR(dst->u_requri, src->u_requri) || - URL_COPYSTR(dst->u_path, src->u_path) || - URL_COPYSTR(dst->u_query, src->u_query) || - URL_COPYSTR(dst->u_fragment, src->u_fragment)) { - nni_url_free(dst); + if (URL_COPYSTR(dst->u_rawurl, src->u_rawurl)) { + NNI_FREE_STRUCT(dst); return (NNG_ENOMEM); } + if (src->u_bufsz != 0) { + if ((dst->u_buffer = nni_alloc(dst->u_bufsz)) == NULL) { + nni_strfree(dst->u_rawurl); + NNI_FREE_STRUCT(dst); + return (NNG_ENOMEM); + } + dst->u_bufsz = src->u_bufsz; + memcpy(dst->u_buffer, src->u_buffer, src->u_bufsz); + } else { + memcpy(dst->u_static, src->u_static, sizeof(src->u_static)); + dst->u_buffer = + dst->u_static + (src->u_buffer - src->u_static); + } + + dst->u_hostname = dst->u_buffer + (src->u_hostname - src->u_buffer); + dst->u_path = dst->u_buffer + (src->u_path - src->u_buffer); + + if (src->u_userinfo != NULL) { + dst->u_userinfo = + dst->u_buffer + (src->u_userinfo - src->u_buffer); + } + if (src->u_query != NULL) { + dst->u_query = dst->u_buffer + (src->u_query - src->u_buffer); + } + if (src->u_fragment != NULL) { + dst->u_fragment = + dst->u_buffer + (src->u_fragment - src->u_buffer); + } dst->u_scheme = src->u_scheme; dst->u_port = src->u_port; *dstp = dst; @@ -683,7 +677,7 @@ nni_url_to_address(nng_sockaddr *sa, const nng_url *url) nni_aio_init(&aio, NULL, NULL); h = url->u_hostname; - if ((h != NULL) && ((strcmp(h, "*") == 0) || (strcmp(h, "") == 0))) { + if ((h != NULL) && (strcmp(h, "") == 0)) { h = NULL; } diff --git a/src/core/url_test.c b/src/core/url_test.c index b5114551c..b8818a3cc 100644 --- a/src/core/url_test.c +++ b/src/core/url_test.c @@ -20,11 +20,9 @@ test_url_host(void) NUTS_PASS(nng_url_parse(&url, "http://www.google.com")); NUTS_ASSERT(url != NULL); NUTS_TRUE(strcmp(url->u_scheme, "http") == 0); - NUTS_TRUE(strcmp(url->u_host, "www.google.com") == 0); NUTS_TRUE(strcmp(url->u_hostname, "www.google.com") == 0); NUTS_TRUE(url->u_port == 80); NUTS_TRUE(strcmp(url->u_path, "") == 0); - NUTS_TRUE(strcmp(url->u_requri, "") == 0); NUTS_TRUE(url->u_query == NULL); NUTS_TRUE(url->u_fragment == NULL); NUTS_TRUE(url->u_userinfo == NULL); @@ -52,11 +50,9 @@ test_url_host_port(void) NUTS_PASS(nng_url_parse(&url, "http://www.google.com:1234")); NUTS_ASSERT(url != NULL); NUTS_TRUE(strcmp(url->u_scheme, "http") == 0); - NUTS_TRUE(strcmp(url->u_host, "www.google.com:1234") == 0); NUTS_TRUE(strcmp(url->u_hostname, "www.google.com") == 0); NUTS_TRUE(url->u_port == 1234); NUTS_TRUE(strcmp(url->u_path, "") == 0); - NUTS_TRUE(strcmp(url->u_requri, "") == 0); NUTS_TRUE(url->u_query == NULL); NUTS_TRUE(url->u_fragment == NULL); NUTS_TRUE(url->u_userinfo == NULL); @@ -71,11 +67,9 @@ test_url_host_port_path(void) NUTS_PASS(nng_url_parse(&url, "http://www.google.com:1234/somewhere")); NUTS_ASSERT(url != NULL); NUTS_TRUE(strcmp(url->u_scheme, "http") == 0); - NUTS_TRUE(strcmp(url->u_host, "www.google.com:1234") == 0); NUTS_TRUE(strcmp(url->u_hostname, "www.google.com") == 0); NUTS_TRUE(url->u_port == 1234); NUTS_TRUE(strcmp(url->u_path, "/somewhere") == 0); - NUTS_TRUE(strcmp(url->u_requri, "/somewhere") == 0); NUTS_TRUE(url->u_userinfo == NULL); NUTS_TRUE(url->u_query == NULL); NUTS_TRUE(url->u_fragment == NULL); @@ -91,11 +85,9 @@ test_url_user_info(void) NUTS_ASSERT(url != NULL); NUTS_MATCH(url->u_scheme, "http"); NUTS_MATCH(url->u_userinfo, "garrett"); - NUTS_MATCH(url->u_host, "www.google.com:1234"); NUTS_MATCH(url->u_hostname, "www.google.com"); NUTS_TRUE(url->u_port == 1234); NUTS_MATCH(url->u_path, "/somewhere"); - NUTS_MATCH(url->u_requri, "/somewhere"); NUTS_NULL(url->u_query); NUTS_NULL(url->u_fragment); nng_url_free(url); @@ -109,12 +101,10 @@ test_url_path_query_param(void) nng_url_parse(&url, "http://www.google.com/somewhere?result=yes")); NUTS_ASSERT(url != NULL); NUTS_MATCH(url->u_scheme, "http"); - NUTS_MATCH(url->u_host, "www.google.com"); NUTS_MATCH(url->u_hostname, "www.google.com"); NUTS_TRUE(url->u_port == 80); NUTS_MATCH(url->u_path, "/somewhere"); NUTS_MATCH(url->u_query, "result=yes"); - NUTS_MATCH(url->u_requri, "/somewhere?result=yes"); NUTS_NULL(url->u_userinfo); NUTS_NULL(url->u_fragment); nng_url_free(url); @@ -129,13 +119,32 @@ test_url_query_param_anchor(void) "somewhere?result=yes#chapter1")); NUTS_ASSERT(url != NULL); NUTS_MATCH(url->u_scheme, "http"); - NUTS_MATCH(url->u_host, "www.google.com"); NUTS_MATCH(url->u_hostname, "www.google.com"); NUTS_TRUE(url->u_port == 80); NUTS_MATCH(url->u_path, "/somewhere"); NUTS_MATCH(url->u_query, "result=yes"); NUTS_MATCH(url->u_fragment, "chapter1"); - NUTS_MATCH(url->u_requri, "/somewhere?result=yes#chapter1"); + NUTS_NULL(url->u_userinfo); + nng_url_free(url); +} + +void +test_url_clone(void) +{ + nng_url *url; + nng_url *src; + NUTS_PASS(nng_url_parse(&src, + "http://www.google.com/" + "somewhere?result=yes#chapter1")); + NUTS_ASSERT(src != NULL); + NUTS_PASS(nng_url_clone(&url, src)); + NUTS_ASSERT(url != NULL); + NUTS_MATCH(url->u_scheme, "http"); + NUTS_MATCH(url->u_hostname, "www.google.com"); + NUTS_TRUE(url->u_port == 80); + NUTS_MATCH(url->u_path, "/somewhere"); + NUTS_MATCH(url->u_query, "result=yes"); + NUTS_MATCH(url->u_fragment, "chapter1"); NUTS_NULL(url->u_userinfo); nng_url_free(url); } @@ -148,12 +157,10 @@ test_url_path_anchor(void) nng_url_parse(&url, "http://www.google.com/somewhere#chapter2")); NUTS_ASSERT(url != NULL); NUTS_MATCH(url->u_scheme, "http"); - NUTS_MATCH(url->u_host, "www.google.com"); NUTS_MATCH(url->u_hostname, "www.google.com"); NUTS_TRUE(url->u_port == 80); NUTS_MATCH(url->u_path, "/somewhere"); NUTS_MATCH(url->u_fragment, "chapter2"); - NUTS_MATCH(url->u_requri, "/somewhere#chapter2"); NUTS_NULL(url->u_query); NUTS_NULL(url->u_userinfo); nng_url_free(url); @@ -166,12 +173,10 @@ test_url_anchor(void) NUTS_PASS(nng_url_parse(&url, "http://www.google.com#chapter3")); NUTS_ASSERT(url != NULL); NUTS_MATCH(url->u_scheme, "http"); - NUTS_MATCH(url->u_host, "www.google.com"); NUTS_MATCH(url->u_hostname, "www.google.com"); NUTS_MATCH(url->u_path, ""); NUTS_TRUE(url->u_port == 80); NUTS_MATCH(url->u_fragment, "chapter3"); - NUTS_MATCH(url->u_requri, "#chapter3"); NUTS_NULL(url->u_query); NUTS_NULL(url->u_userinfo); nng_url_free(url); @@ -184,12 +189,10 @@ test_url_query_param(void) NUTS_PASS(nng_url_parse(&url, "http://www.google.com?color=red")); NUTS_ASSERT(url != NULL); NUTS_MATCH(url->u_scheme, "http"); - NUTS_MATCH(url->u_host, "www.google.com"); NUTS_MATCH(url->u_hostname, "www.google.com"); NUTS_MATCH(url->u_path, ""); NUTS_TRUE(url->u_port == 80); NUTS_MATCH(url->u_query, "color=red"); - NUTS_MATCH(url->u_requri, "?color=red"); NUTS_ASSERT(url != NULL); NUTS_NULL(url->u_userinfo); nng_url_free(url); @@ -202,7 +205,6 @@ test_url_v6_host(void) NUTS_PASS(nng_url_parse(&url, "http://[::1]")); NUTS_ASSERT(url != NULL); NUTS_MATCH(url->u_scheme, "http"); - NUTS_MATCH(url->u_host, "[::1]"); NUTS_MATCH(url->u_hostname, "::1"); NUTS_MATCH(url->u_path, ""); NUTS_TRUE(url->u_port == 80); @@ -219,7 +221,6 @@ test_url_v6_host_port(void) NUTS_PASS(nng_url_parse(&url, "http://[::1]:29")); NUTS_ASSERT(url != NULL); NUTS_MATCH(url->u_scheme, "http"); - NUTS_MATCH(url->u_host, "[::1]:29"); NUTS_MATCH(url->u_hostname, "::1"); NUTS_MATCH(url->u_path, ""); NUTS_TRUE(url->u_port == 29); @@ -236,7 +237,6 @@ test_url_v6_host_port_path(void) NUTS_PASS(nng_url_parse(&url, "http://[::1]:29/bottles")); NUTS_ASSERT(url != NULL); NUTS_MATCH(url->u_scheme, "http"); - NUTS_MATCH(url->u_host, "[::1]:29"); NUTS_MATCH(url->u_hostname, "::1"); NUTS_MATCH(url->u_path, "/bottles"); NUTS_TRUE(url->u_port == 29); @@ -253,7 +253,6 @@ test_url_tcp_port(void) NUTS_PASS(nng_url_parse(&url, "tcp://:9876/")); NUTS_ASSERT(url != NULL); NUTS_MATCH(url->u_scheme, "tcp"); - NUTS_MATCH(url->u_host, ":9876"); NUTS_MATCH(url->u_hostname, ""); NUTS_MATCH(url->u_path, "/"); NUTS_TRUE(url->u_port == 9876); @@ -271,7 +270,6 @@ test_url_bare_ws(void) NUTS_PASS(nng_url_parse(&url, "ws://")); NUTS_ASSERT(url != NULL); NUTS_MATCH(url->u_scheme, "ws"); - NUTS_MATCH(url->u_host, ""); NUTS_MATCH(url->u_hostname, ""); NUTS_MATCH(url->u_path, ""); NUTS_TRUE(url->u_port == 80); @@ -281,23 +279,6 @@ test_url_bare_ws(void) nng_url_free(url); } -void -test_url_ws_wildcard(void) -{ - nng_url *url; - NUTS_PASS(nng_url_parse(&url, "ws://*:12345/foobar")); - NUTS_ASSERT(url != NULL); - NUTS_MATCH(url->u_scheme, "ws"); - NUTS_MATCH(url->u_host, ":12345"); - NUTS_MATCH(url->u_hostname, ""); - NUTS_MATCH(url->u_path, "/foobar"); - NUTS_TRUE(url->u_port == 12345); - NUTS_NULL(url->u_query); - NUTS_NULL(url->u_fragment); - NUTS_NULL(url->u_userinfo); - nng_url_free(url); -} - void test_url_ssh(void) { @@ -305,7 +286,6 @@ test_url_ssh(void) NUTS_PASS(nng_url_parse(&url, "ssh://user@host.example.com")); NUTS_ASSERT(url != NULL); NUTS_MATCH(url->u_scheme, "ssh"); - NUTS_MATCH(url->u_host, "host.example.com"); NUTS_MATCH(url->u_hostname, "host.example.com"); NUTS_MATCH(url->u_path, ""); NUTS_TRUE(url->u_port == 22); @@ -474,7 +454,7 @@ NUTS_TESTS = { { "url v6 host port path", test_url_v6_host_port_path }, { "url tcp port", test_url_tcp_port }, { "url bare ws", test_url_bare_ws }, - { "url ws wildcard", test_url_ws_wildcard }, + { "url clone", test_url_clone }, { "url ssh", test_url_ssh }, { "url bad scheme", test_url_bad_scheme }, { "url bad v6", test_url_bad_ipv6 }, diff --git a/src/platform/posix/posix_resolv_gai.c b/src/platform/posix/posix_resolv_gai.c index 7a0f8e1fd..c26657e90 100644 --- a/src/platform/posix/posix_resolv_gai.c +++ b/src/platform/posix/posix_resolv_gai.c @@ -230,9 +230,12 @@ nni_resolv_ip(const char *host, uint16_t port, int af, bool passive, if (nni_aio_begin(aio) != 0) { return; } - if (host != NULL && strlen(host) >= sizeof(item->host)) { - nni_aio_finish_error(aio, NNG_EADDRINVAL); - return; + if (host != NULL) { + if ((strlen(host) >= sizeof(item->host)) || + (strcmp(host, "*") == 0)) { + nni_aio_finish_error(aio, NNG_EADDRINVAL); + return; + } } switch (af) { case NNG_AF_INET: diff --git a/src/platform/windows/win_resolv.c b/src/platform/windows/win_resolv.c index ece146551..f9e5e1f6e 100644 --- a/src/platform/windows/win_resolv.c +++ b/src/platform/windows/win_resolv.c @@ -190,11 +190,13 @@ nni_resolv_ip(const char *host, uint16_t port, int family, bool passive, if (nni_aio_begin(aio) != 0) { return; } - if (host != NULL && strlen(host) >= sizeof(item->host)) { - nni_aio_finish_error(aio, NNG_EADDRINVAL); - return; + if (host != NULL) { + if ((strlen(host) >= sizeof(item->host)) || + (strcmp(host, "*") == 0)) { + nni_aio_finish_error(aio, NNG_EADDRINVAL); + return; + } } - switch (family) { case NNG_AF_INET: fam = AF_INET; diff --git a/src/sp/transport/socket/sockfd.c b/src/sp/transport/socket/sockfd.c index 2db052acf..2313d12d6 100644 --- a/src/sp/transport/socket/sockfd.c +++ b/src/sp/transport/socket/sockfd.c @@ -817,7 +817,7 @@ sfd_tran_listener_init(void **lp, nng_url *url, nni_listener *nlistener) nni_sock *sock = nni_listener_sock(nlistener); // Check for invalid URL components -- we only accept a bare scheme - if ((strlen(url->u_host) != 0) || (strlen(url->u_path) != 0) || + if ((strlen(url->u_hostname) != 0) || (strlen(url->u_path) != 0) || (url->u_fragment != NULL) || (url->u_userinfo != NULL) || (url->u_query != NULL)) { return (NNG_EADDRINVAL); diff --git a/src/sp/transport/tcp/tcp_test.c b/src/sp/transport/tcp/tcp_test.c index 57c612b79..c80a259ff 100644 --- a/src/sp/transport/tcp/tcp_test.c +++ b/src/sp/transport/tcp/tcp_test.c @@ -10,6 +10,7 @@ // found online at https://opensource.org/licenses/MIT. // +#include "nng/nng.h" #include // TCP tests. @@ -30,19 +31,9 @@ void test_tcp_wild_card_bind(void) { nng_socket s1; - nng_socket s2; - char addr[NNG_MAXADDRLEN]; - uint16_t port; - - port = nuts_next_port(); NUTS_OPEN(s1); - NUTS_OPEN(s2); - (void) snprintf(addr, sizeof(addr), "tcp4://*:%u", port); - NUTS_PASS(nng_listen(s1, addr, NULL, 0)); - (void) snprintf(addr, sizeof(addr), "tcp://127.0.0.1:%u", port); - NUTS_PASS(nng_dial(s2, addr, NULL, 0)); - NUTS_CLOSE(s2); + NUTS_FAIL(nng_listen(s1, "tcp4://*:8080", NULL, 0), NNG_EADDRINVAL); NUTS_CLOSE(s1); } diff --git a/src/sp/transport/tls/tls_tran_test.c b/src/sp/transport/tls/tls_tran_test.c index d6e948996..be049e05a 100644 --- a/src/sp/transport/tls/tls_tran_test.c +++ b/src/sp/transport/tls/tls_tran_test.c @@ -46,58 +46,6 @@ tls_client_config(void) return (c); } -static void -test_tls_wild_card_connect_fail(void) -{ - nng_socket s; - nng_dialer d; - char addr[NNG_MAXADDRLEN]; - - NUTS_OPEN(s); - (void) snprintf( - addr, sizeof(addr), "tls+tcp://*:%u", nuts_next_port()); - NUTS_FAIL(nng_dialer_create(&d, s, addr), NNG_EADDRINVAL); - NUTS_CLOSE(s); -} - -void -test_tls_wild_card_bind(void) -{ - nng_socket s1; - nng_socket s2; - nng_listener l; - nng_dialer d; - char addr[NNG_MAXADDRLEN]; - uint16_t port; - nng_tls_config *cc; - nng_tls_config *sc; - nng_tls_config *other; - - port = nuts_next_port(); - - sc = tls_server_config(); - cc = tls_client_config(); - - NUTS_OPEN(s1); - NUTS_OPEN(s2); - (void) snprintf(addr, sizeof(addr), "tls+tcp4://*:%u", port); - NUTS_PASS(nng_listener_create(&l, s1, addr)); - NUTS_PASS(nng_listener_set_tls(l, sc)); - NUTS_PASS(nng_listener_get_tls(l, &other)); - NUTS_TRUE(sc == other); - NUTS_PASS(nng_listener_start(l, 0)); - (void) snprintf(addr, sizeof(addr), "tls+tcp://127.0.0.1:%u", port); - NUTS_PASS(nng_dialer_create(&d, s2, addr)); - NUTS_PASS(nng_dialer_set_tls(d, cc)); - NUTS_PASS(nng_dialer_get_tls(d, &other)); - NUTS_TRUE(cc == other); - NUTS_PASS(nng_dialer_start(d, 0)); - NUTS_CLOSE(s2); - NUTS_CLOSE(s1); - nng_tls_config_free(cc); - nng_tls_config_free(sc); -} - void test_tls_port_zero_bind(void) { @@ -329,8 +277,6 @@ test_tls_psk(void) NUTS_TESTS = { - { "tls wild card connect fail", test_tls_wild_card_connect_fail }, - { "tls wild card bind", test_tls_wild_card_bind }, { "tls port zero bind", test_tls_port_zero_bind }, { "tls malformed address", test_tls_malformed_address }, { "tls no delay option", test_tls_no_delay_option }, diff --git a/src/sp/transport/udp/udp_tran_test.c b/src/sp/transport/udp/udp_tran_test.c index 151c27830..781c6ad93 100644 --- a/src/sp/transport/udp/udp_tran_test.c +++ b/src/sp/transport/udp/udp_tran_test.c @@ -29,20 +29,9 @@ void test_udp_wild_card_bind(void) { nng_socket s1; - nng_socket s2; - char addr[NNG_MAXADDRLEN]; - uint16_t port; - - port = nuts_next_port(); NUTS_OPEN(s1); - NUTS_OPEN(s2); - (void) snprintf(addr, sizeof(addr), "udp4://*:%u", port); - NUTS_PASS(nng_listen(s1, addr, NULL, 0)); - nng_msleep(500); - (void) snprintf(addr, sizeof(addr), "udp://127.0.0.1:%u", port); - NUTS_PASS(nng_dial(s2, addr, NULL, 0)); - NUTS_CLOSE(s2); + NUTS_FAIL(nng_listen(s1, "udp://*:8080", NULL, 0), NNG_EADDRINVAL); NUTS_CLOSE(s1); } diff --git a/src/sp/transport/ws/ws_test.c b/src/sp/transport/ws/ws_test.c index 0f514cfe7..23158e3f4 100644 --- a/src/sp/transport/ws/ws_test.c +++ b/src/sp/transport/ws/ws_test.c @@ -1,5 +1,5 @@ // -// Copyright 2020 Staysail Systems, Inc. +// Copyright 2024 Staysail Systems, Inc. // Copyright 2018 Cody Piersall // // This software is supplied under the terms of the MIT License, a @@ -99,7 +99,7 @@ test_wild_card_host(void) port = nuts_next_port(); // we use ws4 to ensure 127.0.0.1 binding - snprintf(addr, sizeof(addr), "ws4://*:%u/test", port); + snprintf(addr, sizeof(addr), "ws4://:%u/test", port); NUTS_PASS(nng_listen(s1, addr, NULL, 0)); nng_msleep(100); diff --git a/src/supplemental/http/http_msg.c b/src/supplemental/http/http_msg.c index 28c89ec70..a2ab218f7 100644 --- a/src/supplemental/http/http_msg.c +++ b/src/supplemental/http/http_msg.c @@ -617,8 +617,13 @@ nni_http_req_alloc(nni_http_req **reqp, const nni_url *url) req->uri = NULL; if (url != NULL) { const char *host; + char host_buf[264]; // 256 + 8 for port int rv; - if ((req->uri = nni_strdup(url->u_requri)) == NULL) { + rv = nni_asprintf(&req->uri, "%s%s%s%s%s", url->u_path, + url->u_query ? "?" : "", url->u_query ? url->u_query : "", + url->u_fragment ? "#" : "", + url->u_fragment ? url->u_fragment : ""); + if (rv != 0) { NNI_FREE_STRUCT(req); return (NNG_ENOMEM); } @@ -628,7 +633,14 @@ nni_http_req_alloc(nni_http_req **reqp, const nni_url *url) if (nni_url_default_port(url->u_scheme) == url->u_port) { host = url->u_hostname; } else { - host = url->u_host; + if (strchr(url->u_hostname, ':')) { + snprintf(host_buf, sizeof(host_buf), "[%s]:%u", + url->u_hostname, url->u_port); + } else { + snprintf(host_buf, sizeof(host_buf), "%s:%u", + url->u_hostname, url->u_port); + } + host = host_buf; } if ((rv = nni_http_req_add_header(req, "Host", host)) != 0) { nni_http_req_free(req);