From 85aff44e00e836eda618d4f1cf013bce38b3fd44 Mon Sep 17 00:00:00 2001 From: Garrett D'Amore Date: Sun, 17 Nov 2024 18:23:17 -0800 Subject: URL u_port should be a number not a string. The idea here is to reduce the dynamic allocations used for URLs, and also the back and forth with parsing begin strings and port numbers. We always resolve to a port number, and this is easier for everyone. The real goal in the long term is to eliminate dynamic allocation of the URL fields altogether, but that requires a little more work. This is a step in the right direction. --- docs/ref/api/url.md | 22 ++++----- docs/ref/migrate/nng1.md | 7 +++ include/nng/nng.h | 6 +-- src/core/platform.h | 6 ++- src/core/tcp.c | 24 ++++------ src/core/url.c | 84 ++++++++++++++++++++--------------- src/core/url.h | 16 +++---- src/core/url_test.c | 55 ++++++++++++++--------- src/platform/posix/posix_resolv_gai.c | 73 +++++++++++++++--------------- src/platform/resolver_test.c | 57 ++++++++++++++---------- src/platform/windows/win_resolv.c | 74 +++++++++++++++--------------- src/sp/transport/tcp/tcp.c | 2 +- src/sp/transport/tls/tls.c | 2 +- src/sp/transport/udp/udp.c | 3 +- src/supplemental/http/http_msg.c | 33 +++++++------- src/supplemental/http/http_server.c | 8 ++-- 16 files changed, 253 insertions(+), 219 deletions(-) diff --git a/docs/ref/api/url.md b/docs/ref/api/url.md index 6c104854..0928180e 100644 --- a/docs/ref/api/url.md +++ b/docs/ref/api/url.md @@ -12,16 +12,16 @@ that are not part of the IETF standards. ```c typedef struct nng_url { - char *u_rawurl; - char *u_scheme; - char *u_userinfo; - char *u_host; - char *u_hostname; - char *u_port; - char *u_path; - char *u_query; - char *u_fragment; - char *u_requri; + char *u_rawurl; + 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; ``` @@ -37,7 +37,7 @@ The fields of an `nng_url` object are as follows: - `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 empty if irrelevant or not specified. +- `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. diff --git a/docs/ref/migrate/nng1.md b/docs/ref/migrate/nng1.md index d3440fe4..b7ddea33 100644 --- a/docs/ref/migrate/nng1.md +++ b/docs/ref/migrate/nng1.md @@ -160,4 +160,11 @@ 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 + +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 \* + {{#include ../xref.md}} diff --git a/include/nng/nng.h b/include/nng/nng.h index a41deeb4..6c1eaafd 100644 --- a/include/nng/nng.h +++ b/include/nng/nng.h @@ -1088,9 +1088,9 @@ typedef struct nng_url { char *u_userinfo; // will be NULL if not specified char *u_host; // including colon and port char *u_hostname; // name only, will be "" if not specified - char *u_port; // port, will be "" if not specified - char *u_path; // path, will be "" if not specified - char *u_query; // without '?', will be NULL 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 } nng_url; diff --git a/src/core/platform.h b/src/core/platform.h index 62a321bf..783cc9fc 100644 --- a/src/core/platform.h +++ b/src/core/platform.h @@ -343,7 +343,7 @@ extern int nni_tcp_listener_get( // Symbolic service names will be looked up assuming SOCK_STREAM, so // they may not work with UDP. extern void nni_resolv_ip( - const char *, const char *, int, bool, nng_sockaddr *sa, nni_aio *); + const char *, uint16_t, int, bool, nng_sockaddr *sa, nni_aio *); // nni_parse_ip parses an IP address, without a port. extern int nni_parse_ip(const char *, nng_sockaddr *); @@ -351,6 +351,10 @@ extern int nni_parse_ip(const char *, nng_sockaddr *); // nni_parse_ip_port parses an IP address with an optional port appended. extern int nni_parse_ip_port(const char *, nng_sockaddr *); +// nni_get_port_by_name resolves a name (which may be an ASCII representation +// of a number) to a port number (the value returned is in native byte order.) +extern int nni_get_port_by_name(const char *, uint16_t *); + // // IPC (UNIX Domain Sockets & Named Pipes) Support. // diff --git a/src/core/tcp.c b/src/core/tcp.c index 7fb67228..5d324a13 100644 --- a/src/core/tcp.c +++ b/src/core/tcp.c @@ -1,5 +1,5 @@ // -// Copyright 2020 Staysail Systems, Inc. +// Copyright 2024 Staysail Systems, Inc. // Copyright 2018 Capitar IT Group BV // Copyright 2019 Devolutions // @@ -10,6 +10,7 @@ // #include +#include #include #include @@ -20,8 +21,8 @@ typedef struct { nng_stream_dialer ops; - char *host; - char *port; + char host[256]; + uint16_t port; int af; // address family bool closed; nng_sockaddr sa; @@ -155,8 +156,6 @@ tcp_dialer_free(void *arg) nni_tcp_dialer_fini(d->d); } nni_mtx_fini(&d->mtx); - nni_strfree(d->host); - nni_strfree(d->port); NNI_FREE_STRUCT(d); } @@ -236,17 +235,13 @@ nni_tcp_dialer_alloc(nng_stream_dialer **dp, const nng_url *url) { tcp_dialer *d; int rv; - const char *p; if ((rv = tcp_dialer_alloc(&d)) != 0) { return (rv); } - if (((p = url->u_port) == NULL) || (strlen(p) == 0)) { - p = nni_url_default_port(url->u_scheme); - } - - if ((strlen(p) == 0) || (strlen(url->u_hostname) == 0)) { + if ((url->u_port == 0) || strlen(url->u_hostname) == 0 || + strlen(url->u_hostname) >= sizeof(d->host)) { // Dialer needs both a destination hostname and port. tcp_dialer_free(d); return (NNG_EADDRINVAL); @@ -260,11 +255,8 @@ nni_tcp_dialer_alloc(nng_stream_dialer **dp, const nng_url *url) d->af = NNG_AF_UNSPEC; } - if (((d->host = nng_strdup(url->u_hostname)) == NULL) || - ((d->port = nng_strdup(p)) == NULL)) { - tcp_dialer_free(d); - return (NNG_ENOMEM); - } + snprintf(d->host, sizeof(d->host), "%s", url->u_hostname); + d->port = url->u_port; *dp = (void *) d; return (0); diff --git a/src/core/url.c b/src/core/url.c index c1318719..9bdcfd23 100644 --- a/src/core/url.c +++ b/src/core/url.c @@ -15,6 +15,7 @@ #include #include +#include "core/platform.h" #include "url.h" static uint8_t @@ -234,22 +235,26 @@ url_canonify_uri(char **outp, const char *in) static struct { const char *scheme; - const char *port; + uint16_t port; } nni_url_default_ports[] = { // This list is not exhaustive, but likely covers the main ones we // care about. Feel free to add additional ones as use cases arise. // Note also that we don't use "default" ports for SP protocols // that have no "default" port, like tcp:// or tls+tcp://. // clang-format off - { "git", "9418" }, - { "gopher", "70" }, - { "http", "80" }, - { "https", "443" }, - { "ssh", "22" }, - { "telnet", "23" }, - { "ws", "80" }, - { "wss", "443" }, - { NULL, NULL }, + { "git", 9418 }, + { "gopher", 70 }, + { "http", 80 }, + { "https", 443 }, + { "ssh", 22 }, + { "telnet", 23 }, + { "ws", 80 }, + { "ws4", 80 }, + { "ws6", 80 }, + { "wss", 443 }, + { "wss4", 443 }, + { "wss6", 443 }, + { NULL, 0 }, // clang-format on }; @@ -274,6 +279,9 @@ static const char *nni_schemes[] = { "wss", "wss4", "wss6", + "udp", + "udp4", + "udp6", // we don't support these "file", "mailto", @@ -284,10 +292,11 @@ static const char *nni_schemes[] = { "telnet", "irc", "imap", + "imaps", NULL, }; -const char * +uint16_t nni_url_default_port(const char *scheme) { const char *s; @@ -310,7 +319,7 @@ nni_url_default_port(const char *scheme) break; } } - return (""); + return (0); } // URLs usually follow the following format: @@ -503,6 +512,11 @@ nni_url_parse(nni_url **urlp, const char *raw) } } } + // hostname length check + if (len >= 256) { + rv = NNG_EADDRINVAL; + goto error; + } if ((url->u_hostname = nni_alloc(len + 1)) == NULL) { rv = NNG_ENOMEM; goto error; @@ -521,13 +535,12 @@ nni_url_parse(nni_url **urlp, const char *raw) rv = NNG_EINVAL; goto error; } - url->u_port = nni_strdup(s + 1); + rv = nni_get_port_by_name(s + 1, &url->u_port); + if (rv != 0) { + goto error; + } } else { - url->u_port = nni_strdup(nni_url_default_port(url->u_scheme)); - } - if (url->u_port == NULL) { - rv = NNG_ENOMEM; - goto error; + url->u_port = nni_url_default_port(url->u_scheme); } *urlp = url; @@ -546,7 +559,6 @@ nni_url_free(nni_url *url) nni_strfree(url->u_userinfo); nni_strfree(url->u_host); nni_strfree(url->u_hostname); - nni_strfree(url->u_port); nni_strfree(url->u_path); nni_strfree(url->u_query); nni_strfree(url->u_fragment); @@ -558,11 +570,11 @@ nni_url_free(nni_url *url) int nni_url_asprintf(char **str, const nni_url *url) { - const char *scheme = url->u_scheme; - const char *port = url->u_port; - const char *host = url->u_hostname; - const char *hostob = ""; - const char *hostcb = ""; + const char *scheme = url->u_scheme; + const char *host = url->u_hostname; + const char *hostob = ""; + const char *hostcb = ""; + bool do_port = true; if ((strcmp(scheme, "ipc") == 0) || (strcmp(scheme, "inproc") == 0) || (strcmp(scheme, "unix") == 0) || @@ -570,11 +582,8 @@ nni_url_asprintf(char **str, const nni_url *url) return (nni_asprintf(str, "%s://%s", scheme, url->u_path)); } - if (port != NULL) { - if ((strlen(port) == 0) || - (strcmp(nni_url_default_port(scheme), port) == 0)) { - port = NULL; - } + if (url->u_port == nni_url_default_port(scheme)) { + do_port = false; } if (strcmp(host, "*") == 0) { host = ""; @@ -583,9 +592,14 @@ nni_url_asprintf(char **str, const nni_url *url) hostob = "["; hostcb = "]"; } - return (nni_asprintf(str, "%s://%s%s%s%s%s%s", scheme, hostob, host, - hostcb, port != NULL ? ":" : "", port != NULL ? port : "", - url->u_requri != NULL ? url->u_requri : "")); + char portstr[8]; + if (do_port) { + snprintf(portstr, sizeof(portstr), ":%u", url->u_port); + } else { + portstr[0] = 0; + } + return (nni_asprintf(str, "%s://%s%s%s%s%s", scheme, hostob, host, + hostcb, portstr, url->u_requri != NULL ? url->u_requri : "")); } // nni_url_asprintf_port is like nni_url_asprintf, but includes a port @@ -594,12 +608,10 @@ nni_url_asprintf(char **str, const nni_url *url) int nni_url_asprintf_port(char **str, const nni_url *url, int port) { - char portstr[16]; nni_url myurl = *url; if (port > 0) { - (void) snprintf(portstr, sizeof(portstr), "%d", port); - myurl.u_port = portstr; + myurl.u_port = (uint16_t) port; } return (nni_url_asprintf(str, &myurl)); } @@ -618,7 +630,6 @@ nni_url_clone(nni_url **dstp, const nni_url *src) 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_port, src->u_port) || URL_COPYSTR(dst->u_requri, src->u_requri) || URL_COPYSTR(dst->u_path, src->u_path) || URL_COPYSTR(dst->u_query, src->u_query) || @@ -627,6 +638,7 @@ nni_url_clone(nni_url **dstp, const nni_url *src) return (NNG_ENOMEM); } dst->u_scheme = src->u_scheme; + dst->u_port = src->u_port; *dstp = dst; return (0); } diff --git a/src/core/url.h b/src/core/url.h index 1761f6fd..71a093d0 100644 --- a/src/core/url.h +++ b/src/core/url.h @@ -13,13 +13,13 @@ #include "core/defs.h" -extern int nni_url_parse(nni_url **, const char *path); -extern void nni_url_free(nni_url *); -extern int nni_url_clone(nni_url **, const nni_url *); -extern const char *nni_url_default_port(const char *); -extern int nni_url_asprintf(char **, const nni_url *); -extern int nni_url_asprintf_port(char **, const nni_url *, int); -extern size_t nni_url_decode(uint8_t *, const char *, size_t); -extern int nni_url_to_address(nng_sockaddr *, const nni_url *); +extern int nni_url_parse(nni_url **, const char *path); +extern void nni_url_free(nni_url *); +extern int nni_url_clone(nni_url **, const nni_url *); +extern uint16_t nni_url_default_port(const char *); +extern int nni_url_asprintf(char **, const nni_url *); +extern int nni_url_asprintf_port(char **, const nni_url *, int); +extern size_t nni_url_decode(uint8_t *, const char *, size_t); +extern int nni_url_to_address(nng_sockaddr *, const nni_url *); #endif // CORE_URL_H diff --git a/src/core/url_test.c b/src/core/url_test.c index 232cabc8..b5114551 100644 --- a/src/core/url_test.c +++ b/src/core/url_test.c @@ -22,7 +22,7 @@ test_url_host(void) 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(strcmp(url->u_port, "80") == 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); @@ -31,6 +31,20 @@ test_url_host(void) nng_url_free(url); } +void +test_url_host_too_long(void) +{ + nng_url *url; + char buffer[512]; // + + memset(buffer, 0, sizeof(buffer)); + snprintf(buffer, sizeof(buffer), "http://"); + for (size_t i = strlen(buffer); i < sizeof(buffer) - 1; i++) { + buffer[i] = 'a'; + } + NUTS_FAIL(nng_url_parse(&url, buffer), NNG_EADDRINVAL); +} + void test_url_host_port(void) { @@ -40,7 +54,7 @@ test_url_host_port(void) 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(strcmp(url->u_port, "1234") == 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); @@ -59,7 +73,7 @@ test_url_host_port_path(void) 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(strcmp(url->u_port, "1234") == 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); @@ -79,7 +93,7 @@ test_url_user_info(void) NUTS_MATCH(url->u_userinfo, "garrett"); NUTS_MATCH(url->u_host, "www.google.com:1234"); NUTS_MATCH(url->u_hostname, "www.google.com"); - NUTS_MATCH(url->u_port, "1234"); + NUTS_TRUE(url->u_port == 1234); NUTS_MATCH(url->u_path, "/somewhere"); NUTS_MATCH(url->u_requri, "/somewhere"); NUTS_NULL(url->u_query); @@ -97,7 +111,7 @@ test_url_path_query_param(void) 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_port, "80"); + 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"); @@ -117,7 +131,7 @@ test_url_query_param_anchor(void) 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_port, "80"); + 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"); @@ -136,7 +150,7 @@ test_url_path_anchor(void) 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_port, "80"); + 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"); @@ -155,7 +169,7 @@ test_url_anchor(void) NUTS_MATCH(url->u_host, "www.google.com"); NUTS_MATCH(url->u_hostname, "www.google.com"); NUTS_MATCH(url->u_path, ""); - NUTS_MATCH(url->u_port, "80"); + NUTS_TRUE(url->u_port == 80); NUTS_MATCH(url->u_fragment, "chapter3"); NUTS_MATCH(url->u_requri, "#chapter3"); NUTS_NULL(url->u_query); @@ -173,7 +187,7 @@ test_url_query_param(void) NUTS_MATCH(url->u_host, "www.google.com"); NUTS_MATCH(url->u_hostname, "www.google.com"); NUTS_MATCH(url->u_path, ""); - NUTS_MATCH(url->u_port, "80"); + NUTS_TRUE(url->u_port == 80); NUTS_MATCH(url->u_query, "color=red"); NUTS_MATCH(url->u_requri, "?color=red"); NUTS_ASSERT(url != NULL); @@ -191,7 +205,7 @@ test_url_v6_host(void) NUTS_MATCH(url->u_host, "[::1]"); NUTS_MATCH(url->u_hostname, "::1"); NUTS_MATCH(url->u_path, ""); - NUTS_MATCH(url->u_port, "80"); + NUTS_TRUE(url->u_port == 80); NUTS_NULL(url->u_query); NUTS_NULL(url->u_fragment); NUTS_NULL(url->u_userinfo); @@ -208,7 +222,7 @@ test_url_v6_host_port(void) NUTS_MATCH(url->u_host, "[::1]:29"); NUTS_MATCH(url->u_hostname, "::1"); NUTS_MATCH(url->u_path, ""); - NUTS_MATCH(url->u_port, "29"); + NUTS_TRUE(url->u_port == 29); NUTS_NULL(url->u_query); NUTS_NULL(url->u_fragment); NUTS_NULL(url->u_userinfo); @@ -225,7 +239,7 @@ test_url_v6_host_port_path(void) NUTS_MATCH(url->u_host, "[::1]:29"); NUTS_MATCH(url->u_hostname, "::1"); NUTS_MATCH(url->u_path, "/bottles"); - NUTS_MATCH(url->u_port, "29"); + NUTS_TRUE(url->u_port == 29); NUTS_NULL(url->u_query); NUTS_NULL(url->u_fragment); NUTS_NULL(url->u_userinfo); @@ -242,7 +256,7 @@ test_url_tcp_port(void) NUTS_MATCH(url->u_host, ":9876"); NUTS_MATCH(url->u_hostname, ""); NUTS_MATCH(url->u_path, "/"); - NUTS_MATCH(url->u_port, "9876"); + NUTS_TRUE(url->u_port == 9876); NUTS_NULL(url->u_query); NUTS_NULL(url->u_fragment); NUTS_NULL(url->u_userinfo); @@ -260,7 +274,7 @@ test_url_bare_ws(void) NUTS_MATCH(url->u_host, ""); NUTS_MATCH(url->u_hostname, ""); NUTS_MATCH(url->u_path, ""); - NUTS_MATCH(url->u_port, "80"); + NUTS_TRUE(url->u_port == 80); NUTS_NULL(url->u_query); NUTS_NULL(url->u_fragment); NUTS_NULL(url->u_userinfo); @@ -277,7 +291,7 @@ test_url_ws_wildcard(void) NUTS_MATCH(url->u_host, ":12345"); NUTS_MATCH(url->u_hostname, ""); NUTS_MATCH(url->u_path, "/foobar"); - NUTS_MATCH(url->u_port, "12345"); + NUTS_TRUE(url->u_port == 12345); NUTS_NULL(url->u_query); NUTS_NULL(url->u_fragment); NUTS_NULL(url->u_userinfo); @@ -294,7 +308,7 @@ test_url_ssh(void) NUTS_MATCH(url->u_host, "host.example.com"); NUTS_MATCH(url->u_hostname, "host.example.com"); NUTS_MATCH(url->u_path, ""); - NUTS_MATCH(url->u_port, "22"); + NUTS_TRUE(url->u_port == 22); NUTS_NULL(url->u_query); NUTS_NULL(url->u_fragment); NUTS_MATCH(url->u_userinfo, "user"); @@ -332,7 +346,7 @@ test_url_canonify(void) NUTS_ASSERT(url != NULL); NUTS_MATCH(url->u_scheme, "http"); NUTS_MATCH(url->u_hostname, "www.example.com"); - NUTS_MATCH(url->u_port, "80"); + NUTS_TRUE(url->u_port == 80); NUTS_MATCH(url->u_path, "/~garrett"); nng_url_free(url); } @@ -345,7 +359,7 @@ test_url_path_resolve(void) nng_url_parse(&url, "http://www.x.com//abc/def/./x/..///./../y")); NUTS_MATCH(url->u_scheme, "http"); NUTS_MATCH(url->u_hostname, "www.x.com"); - NUTS_MATCH(url->u_port, "80"); + NUTS_TRUE(url->u_port == 80); NUTS_MATCH(url->u_path, "/abc/y"); nng_url_free(url); } @@ -359,7 +373,7 @@ test_url_query_info_pass(void) NUTS_ASSERT(url != NULL); NUTS_MATCH(url->u_scheme, "http"); NUTS_MATCH(url->u_hostname, "www.x.com"); - NUTS_MATCH(url->u_port, "80"); + NUTS_TRUE(url->u_port == 80); NUTS_MATCH(url->u_path, "/"); NUTS_MATCH(url->u_query, "/abc/def/./x/.././../y"); nng_url_free(url); @@ -383,7 +397,7 @@ test_url_good_utf8(void) NUTS_ASSERT(url != NULL); NUTS_MATCH(url->u_scheme, "http"); NUTS_MATCH(url->u_hostname, "www.x.com"); - NUTS_MATCH(url->u_port, "80"); + NUTS_TRUE(url->u_port == 80); NUTS_MATCH(url->u_path, "/\xc2\xa2_cents"); nng_url_free(url); } @@ -446,6 +460,7 @@ test_url_decode(void) NUTS_TESTS = { { "url host", test_url_host }, + { "url host too long", test_url_host_too_long }, { "url host port", test_url_host_port }, { "url host port path", test_url_host_port_path }, { "url user info", test_url_user_info }, diff --git a/src/platform/posix/posix_resolv_gai.c b/src/platform/posix/posix_resolv_gai.c index f85c27c1..7a0f8e1f 100644 --- a/src/platform/posix/posix_resolv_gai.c +++ b/src/platform/posix/posix_resolv_gai.c @@ -10,6 +10,7 @@ #include "core/init.h" #include "core/nng_impl.h" +#include "nng/nng.h" #ifdef NNG_USE_POSIX_RESOLV_GAI @@ -17,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -49,8 +51,8 @@ typedef struct resolv_item resolv_item; struct resolv_item { int family; bool passive; - char *host; - char *serv; + char host[256]; + char serv[8]; nni_aio *aio; nng_sockaddr *sa; }; @@ -58,8 +60,6 @@ struct resolv_item { static void resolv_free_item(resolv_item *item) { - nni_strfree(item->serv); - nni_strfree(item->host); NNI_FREE_STRUCT(item); } @@ -155,29 +155,12 @@ resolv_task(resolv_item *item) } hints.ai_family = item->family; hints.ai_socktype = SOCK_STREAM; - - // Check to see if this is a numeric port number, and if it is - // make sure that it's in the valid range (because Windows may - // incorrectly simple do a conversion and mask off upper bits. - if (item->serv != NULL) { - long port; - char *end; - port = strtol(item->serv, &end, 10); - if (*end == '\0') { // we fully converted it as a number... - hints.ai_flags |= AI_NUMERICSERV; - - // Not a valid port number. Fail. - if ((port < 0) || (port > 0xffff)) { - rv = NNG_EADDRINVAL; - goto done; - } - } - } + hints.ai_flags |= AI_NUMERICSERV; // We can pass any non-zero service number, but we have to pass // *something*, in case we are using a NULL hostname. - if ((rv = getaddrinfo(item->host, item->serv, &hints, &results)) != - 0) { + if ((rv = getaddrinfo(item->host[0] != 0 ? item->host : NULL, + item->serv, &hints, &results)) != 0) { rv = posix_gai_errno(rv); goto done; } @@ -237,7 +220,7 @@ done: } void -nni_resolv_ip(const char *host, const char *serv, int af, bool passive, +nni_resolv_ip(const char *host, uint16_t port, int af, bool passive, nng_sockaddr *sa, nni_aio *aio) { resolv_item *item; @@ -247,6 +230,10 @@ nni_resolv_ip(const char *host, const char *serv, 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; + } switch (af) { case NNG_AF_INET: fam = AF_INET; @@ -275,19 +262,11 @@ nni_resolv_ip(const char *host, const char *serv, int af, bool passive, return; } - if (serv == NULL || strcmp(serv, "") == 0) { - item->serv = NULL; - } else if ((item->serv = nni_strdup(serv)) == NULL) { - nni_aio_finish_error(aio, NNG_ENOMEM); - resolv_free_item(item); - return; - } + snprintf(item->serv, sizeof(item->serv), "%u", port); if (host == NULL) { - item->host = NULL; - } else if ((item->host = nni_strdup(host)) == NULL) { - nni_aio_finish_error(aio, NNG_ENOMEM); - resolv_free_item(item); - return; + item->host[0] = '\0'; + } else { + snprintf(item->host, sizeof(item->host), "%s", host); } item->aio = aio; @@ -477,6 +456,26 @@ nni_parse_ip_port(const char *addr, nni_sockaddr *sa) return (parse_ip(addr, sa, true)); } +int +nni_get_port_by_name(const char *name, uint16_t *portp) +{ + struct servent *se; + long port; + char *end = NULL; + + port = strtol(name, &end, 10); + if ((*end == '\0') && (port >= 0) && (port <= 0xffff)) { + *portp = (uint16_t) port; + return (0); + } + + if ((se = getservbyname(name, "tcp")) != NULL) { + *portp = (uint16_t) ntohs(se->s_port); + return (0); + } + return (NNG_EADDRINVAL); +} + int nni_posix_resolv_sysinit(nng_init_params *params) { diff --git a/src/platform/resolver_test.c b/src/platform/resolver_test.c index 64554291..9c7db2c0 100644 --- a/src/platform/resolver_test.c +++ b/src/platform/resolver_test.c @@ -9,6 +9,7 @@ // #include "core/nng_impl.h" +#include "core/platform.h" #include @@ -41,8 +42,8 @@ test_google_dns(void) nng_sockaddr sa; NUTS_PASS(nng_aio_alloc(&aio, NULL, NULL)); - nni_resolv_ip("google-public-dns-a.google.com", "80", NNG_AF_INET, - true, &sa, aio); + nni_resolv_ip( + "google-public-dns-a.google.com", 80, NNG_AF_INET, true, &sa, aio); nng_aio_wait(aio); NUTS_PASS(nng_aio_result(aio)); NUTS_TRUE(sa.s_in.sa_family == NNG_AF_INET); @@ -51,6 +52,22 @@ test_google_dns(void) nng_aio_free(aio); } +void +test_hostname_too_long(void) +{ + nng_aio *aio; + nng_sockaddr sa; + char buffer[512]; + + memset(buffer, 'a', sizeof(buffer) - 1); + buffer[sizeof(buffer) - 1] = '\0'; + NUTS_PASS(nng_aio_alloc(&aio, NULL, NULL)); + nni_resolv_ip(buffer, 80, NNG_AF_INET, true, &sa, aio); + nng_aio_wait(aio); + NUTS_FAIL(nng_aio_result(aio), NNG_EADDRINVAL); + nng_aio_free(aio); +} + void test_numeric_addr(void) { @@ -58,7 +75,7 @@ test_numeric_addr(void) nng_sockaddr sa; NUTS_PASS(nng_aio_alloc(&aio, NULL, NULL)); - nni_resolv_ip("8.8.4.4", "69", NNG_AF_INET, true, &sa, aio); + nni_resolv_ip("8.8.4.4", 69, NNG_AF_INET, true, &sa, aio); nng_aio_wait(aio); NUTS_PASS(nng_aio_result(aio)); NUTS_TRUE(sa.s_in.sa_family == NNG_AF_INET); @@ -79,7 +96,7 @@ test_numeric_v6(void) } NUTS_MSG("IPV6 support present"); NUTS_PASS(nng_aio_alloc(&aio, NULL, NULL)); - nni_resolv_ip("::1", "80", NNG_AF_INET6, true, &sa, aio); + nni_resolv_ip("::1", 80, NNG_AF_INET6, true, &sa, aio); nng_aio_wait(aio); NUTS_PASS(nng_aio_result(aio)); NUTS_TRUE(sa.s_in6.sa_family == NNG_AF_INET6); @@ -94,14 +111,21 @@ test_service_names(void) { nng_aio *aio; nng_sockaddr sa; + uint16_t port; NUTS_PASS(nng_aio_alloc(&aio, NULL, NULL)); - nni_resolv_ip("8.8.4.4", "http", NNG_AF_INET, true, &sa, aio); + nni_resolv_ip("8.8.4.4", 80, NNG_AF_INET, true, &sa, aio); nng_aio_wait(aio); NUTS_PASS(nng_aio_result(aio)); NUTS_TRUE(sa.s_in.sa_port == nuts_be16(80)); NUTS_TRUE(sa.s_in.sa_addr = nuts_be32(0x08080404)); nng_aio_free(aio); + + NUTS_PASS(nni_get_port_by_name("http", &port)); + NUTS_TRUE(port == 80); + + NUTS_PASS(nni_get_port_by_name("25", &port)); + NUTS_TRUE(port == 25); } void @@ -111,7 +135,7 @@ test_localhost_v4(void) nng_sockaddr sa; NUTS_PASS(nng_aio_alloc(&aio, NULL, NULL)); - nni_resolv_ip("localhost", "80", NNG_AF_INET, true, &sa, aio); + nni_resolv_ip("localhost", 80, NNG_AF_INET, true, &sa, aio); nng_aio_wait(aio); NUTS_PASS(nng_aio_result(aio)); NUTS_TRUE(sa.s_in.sa_family == NNG_AF_INET); @@ -127,7 +151,7 @@ test_localhost_unspecified(void) nng_sockaddr sa; NUTS_PASS(nng_aio_alloc(&aio, NULL, NULL)); - nni_resolv_ip("localhost", "80", NNG_AF_UNSPEC, true, &sa, aio); + nni_resolv_ip("localhost", 80, NNG_AF_UNSPEC, true, &sa, aio); nng_aio_wait(aio); NUTS_PASS(nng_aio_result(aio)); NUTS_TRUE( @@ -154,7 +178,7 @@ test_null_passive(void) nng_sockaddr sa; NUTS_PASS(nng_aio_alloc(&aio, NULL, NULL)); - nni_resolv_ip(NULL, "80", NNG_AF_INET, true, &sa, aio); + nni_resolv_ip(NULL, 80, NNG_AF_INET, true, &sa, aio); nng_aio_wait(aio); NUTS_PASS(nng_aio_result(aio)); NUTS_TRUE(sa.s_in.sa_family == NNG_AF_INET); @@ -170,7 +194,7 @@ test_null_not_passive(void) nng_sockaddr sa; NUTS_PASS(nng_aio_alloc(&aio, NULL, NULL)); - nni_resolv_ip(NULL, "80", NNG_AF_INET, false, &sa, aio); + nni_resolv_ip(NULL, 80, NNG_AF_INET, false, &sa, aio); nng_aio_wait(aio); // We can either get invalid address, or a loopback address. // Most systems do the former, but Linux does the latter. @@ -184,21 +208,9 @@ test_null_not_passive(void) nng_aio_free(aio); } -void -test_bad_port_number(void) -{ - nng_aio *aio; - nng_sockaddr sa; - - NUTS_PASS(nng_aio_alloc(&aio, NULL, NULL)); - nni_resolv_ip("1.1.1.1", "1000000", NNG_AF_INET, true, &sa, aio); - nng_aio_wait(aio); - NUTS_FAIL(nng_aio_result(aio), NNG_EADDRINVAL); - nng_aio_free(aio); -} - NUTS_TESTS = { { "resolve google dns", test_google_dns }, + { "resolve hostname too long", test_hostname_too_long }, { "resolve numeric addr", test_numeric_addr }, #ifdef NNG_ENABLE_IPV6 { "resolve numeric v6", test_numeric_v6 }, @@ -208,6 +220,5 @@ NUTS_TESTS = { { "resolve localhost unspecified", test_localhost_unspecified }, { "resolve null passive", test_null_passive }, { "resolve null not passive", test_null_not_passive }, - { "resolve bad port number", test_bad_port_number }, { NULL, NULL }, }; diff --git a/src/platform/windows/win_resolv.c b/src/platform/windows/win_resolv.c index 1b1ae7b9..ece14655 100644 --- a/src/platform/windows/win_resolv.c +++ b/src/platform/windows/win_resolv.c @@ -33,8 +33,8 @@ typedef struct resolv_item resolv_item; struct resolv_item { int family; bool passive; - char *host; - char *serv; + char host[256]; + char serv[8]; nni_aio *aio; nng_sockaddr *sa; }; @@ -42,8 +42,6 @@ struct resolv_item { static void resolv_free_item(resolv_item *item) { - nni_strfree(item->serv); - nni_strfree(item->host); NNI_FREE_STRUCT(item); } @@ -118,27 +116,10 @@ resolv_task(resolv_item *item) } hints.ai_family = item->family; hints.ai_socktype = SOCK_STREAM; + hints.ai_flags |= AI_NUMERICSERV; - // Check to see if this is a numeric port number, and if it is - // make sure that it's in the valid range (because Windows may - // incorrectly simple do a conversion and mask off upper bits. - if (item->serv != NULL) { - long port; - char *end; - port = strtol(item->serv, &end, 10); - if (*end == '\0') { // we fully converted it as a number... - hints.ai_flags |= AI_NUMERICSERV; - - // Not a valid port number. Fail. - if ((port < 0) || (port > 0xffff)) { - rv = NNG_EADDRINVAL; - goto done; - } - } - } - - if ((rv = getaddrinfo(item->host, item->serv, &hints, &results)) != - 0) { + if ((rv = getaddrinfo(item->host[0] != 0 ? item->host : NULL, + item->serv, &hints, &results)) != 0) { rv = resolv_errno(rv); goto done; } @@ -199,7 +180,7 @@ done: } void -nni_resolv_ip(const char *host, const char *serv, int family, bool passive, +nni_resolv_ip(const char *host, uint16_t port, int family, bool passive, nng_sockaddr *sa, nni_aio *aio) { resolv_item *item; @@ -209,6 +190,11 @@ nni_resolv_ip(const char *host, const char *serv, 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; + } + switch (family) { case NNG_AF_INET: fam = AF_INET; @@ -234,20 +220,12 @@ nni_resolv_ip(const char *host, const char *serv, int family, bool passive, nni_aio_finish_error(aio, NNG_ENOMEM); return; } - if (host == NULL) { - item->host = NULL; - } else if ((item->host = nni_strdup(host)) == NULL) { - nni_aio_finish_error(aio, NNG_ENOMEM); - resolv_free_item(item); - return; - } - if (serv == NULL) { - item->serv = NULL; - } else if ((item->serv = nni_strdup(serv)) == NULL) { - nni_aio_finish_error(aio, NNG_ENOMEM); - resolv_free_item(item); - return; + snprintf(item->serv, sizeof(item->serv), "%u", port); + if (host == NULL) { + item->host[0] = '\0'; + } else { + snprintf(item->host, sizeof(item->host), "%s", host); } item->sa = sa; @@ -431,6 +409,26 @@ nni_parse_ip_port(const char *addr, nni_sockaddr *sa) return (parse_ip(addr, sa, true)); } +int +nni_get_port_by_name(const char *name, uint16_t *portp) +{ + struct servent *se; + long port; + char *end = NULL; + + port = strtol(name, &end, 10); + if ((*end == '\0') && (port >= 0) && (port <= 0xffff)) { + *portp = (uint16_t) port; + return (0); + } + + if ((se = getservbyname(name, "tcp")) != NULL) { + *portp = (uint16_t) ntohs(se->s_port); + return (0); + } + return (NNG_EADDRINVAL); +} + int nni_win_resolv_sysinit(nng_init_params *params) { diff --git a/src/sp/transport/tcp/tcp.c b/src/sp/transport/tcp/tcp.c index 54d31726..86d0ac07 100644 --- a/src/sp/transport/tcp/tcp.c +++ b/src/sp/transport/tcp/tcp.c @@ -874,7 +874,7 @@ tcptran_dialer_init(void **dp, nng_url *url, nni_dialer *ndialer) } if ((url->u_fragment != NULL) || (url->u_userinfo != NULL) || (url->u_query != NULL) || (strlen(url->u_hostname) == 0) || - (strlen(url->u_port) == 0)) { + (url->u_port == 0)) { return (NNG_EADDRINVAL); } diff --git a/src/sp/transport/tls/tls.c b/src/sp/transport/tls/tls.c index 7a1d5582..b1aea7b3 100644 --- a/src/sp/transport/tls/tls.c +++ b/src/sp/transport/tls/tls.c @@ -837,7 +837,7 @@ tlstran_ep_init_dialer(void **dp, nni_url *url, nni_dialer *ndialer) } if ((url->u_fragment != NULL) || (url->u_userinfo != NULL) || (url->u_query != NULL) || (strlen(url->u_hostname) == 0) || - (strlen(url->u_port) == 0)) { + (url->u_port == 0)) { return (NNG_EADDRINVAL); } diff --git a/src/sp/transport/udp/udp.c b/src/sp/transport/udp/udp.c index 25899070..29a516d8 100644 --- a/src/sp/transport/udp/udp.c +++ b/src/sp/transport/udp/udp.c @@ -1443,8 +1443,7 @@ udp_check_url(nng_url *url, bool listen) return (NNG_EADDRINVAL); } if (!listen) { - if ((strlen(url->u_hostname) == 0) || - (strlen(url->u_port) == 0) || (atoi(url->u_port) == 0)) { + if ((strlen(url->u_hostname) == 0) || (url->u_port == 0)) { return (NNG_EADDRINVAL); } } diff --git a/src/supplemental/http/http_msg.c b/src/supplemental/http/http_msg.c index 9f5996ca..28c89ec7 100644 --- a/src/supplemental/http/http_msg.c +++ b/src/supplemental/http/http_msg.c @@ -22,13 +22,13 @@ // a comma. From experience, for example, Firefox uses a Connection: // header with two values, "keepalive", and "upgrade". typedef struct http_header { - char * name; - char * value; + char *name; + char *value; nni_list_node node; } http_header; typedef struct nni_http_entity { - char * data; + char *data; size_t size; // allocated/expected size size_t len; // current length bool own; // if true, data is "ours", and should be freed @@ -37,10 +37,10 @@ typedef struct nni_http_entity { struct nng_http_req { nni_list hdrs; nni_http_entity data; - char * meth; - char * uri; - char * vers; - char * buf; + char *meth; + char *uri; + char *vers; + char *buf; size_t bufsz; bool parsed; }; @@ -49,9 +49,9 @@ struct nng_http_res { nni_list hdrs; nni_http_entity data; uint16_t code; - char * rsn; - char * vers; - char * buf; + char *rsn; + char *vers; + char *buf; size_t bufsz; bool parsed; bool iserr; @@ -492,7 +492,7 @@ http_asprintf(char **bufp, size_t *szp, nni_list *hdrs, const char *fmt, ...) va_list ap; size_t len; size_t n; - char * buf; + char *buf; va_start(ap, fmt); len = vsnprintf(NULL, 0, fmt, ap); @@ -550,7 +550,7 @@ http_res_prepare(nni_http_res *res) char * nni_http_req_headers(nni_http_req *req) { - char * s; + char *s; size_t len; len = http_sprintf_headers(NULL, 0, &req->hdrs) + 1; @@ -563,7 +563,7 @@ nni_http_req_headers(nni_http_req *req) char * nni_http_res_headers(nni_http_res *res) { - char * s; + char *s; size_t len; len = http_sprintf_headers(NULL, 0, &res->hdrs) + 1; @@ -625,8 +625,7 @@ nni_http_req_alloc(nni_http_req **reqp, const nni_url *url) // Add a Host: header since we know that from the URL. Also, // only include the :port portion if it isn't the default port. - if (strcmp(nni_url_default_port(url->u_scheme), url->u_port) == - 0) { + if (nni_url_default_port(url->u_scheme) == url->u_port) { host = url->u_hostname; } else { host = url->u_host; @@ -735,7 +734,7 @@ http_scan_line(void *vbuf, size_t n, size_t *lenp) { size_t len; char lc; - char * buf = vbuf; + char *buf = vbuf; lc = 0; for (len = 0; len < n; len++) { @@ -1042,7 +1041,7 @@ nni_http_alloc_html_error(char **html, uint16_t code, const char *details) int nni_http_res_alloc_error(nni_http_res **resp, uint16_t err) { - char * html = NULL; + char *html = NULL; nni_http_res *res = NULL; int rv; diff --git a/src/supplemental/http/http_server.c b/src/supplemental/http/http_server.c index ac57cf5b..03b3cf68 100644 --- a/src/supplemental/http/http_server.c +++ b/src/supplemental/http/http_server.c @@ -1,5 +1,5 @@ // -// Copyright 2024 Staysail Systems, Inc. +// Copyright 2023 Staysail Systems, Inc. // Copyright 2018 Capitar IT Group BV // Copyright 2018 QXSoftware // Copyright 2019 Devolutions @@ -962,9 +962,7 @@ http_server_init(nni_http_server **serverp, const nni_url *url) return (rv); } - // NB: We only support number port numbers, and the URL framework - // expands empty port numbers to 80 or 443 as appropriate. - s->port = atoi(url->u_port); + s->port = url->u_port; if ((s->hostname = nni_strdup(url->u_hostname)) == NULL) { http_server_fini(s); @@ -989,7 +987,7 @@ nni_http_server_init(nni_http_server **serverp, const nni_url *url) nni_mtx_lock(&http_servers_lk); NNI_LIST_FOREACH (&http_servers, s) { - if ((!s->closed) && (atoi(url->u_port) == s->port) && + if ((!s->closed) && (url->u_port == s->port) && (strcmp(url->u_hostname, s->hostname) == 0)) { *serverp = s; s->refcnt++; -- cgit v1.2.3-70-g09d2