diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/CMakeLists.txt | 2 | ||||
| -rw-r--r-- | src/core/nng_impl.h | 1 | ||||
| -rw-r--r-- | src/core/transport.h | 2 | ||||
| -rw-r--r-- | src/core/url.c | 367 | ||||
| -rw-r--r-- | src/core/url.h | 52 | ||||
| -rw-r--r-- | src/transport/tls/tls.c | 172 |
6 files changed, 480 insertions, 116 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 45dcb4fe..a341a8c5 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -76,6 +76,8 @@ set (NNG_SOURCES core/timer.h core/transport.c core/transport.h + core/url.c + core/url.h ) if (NNG_PLATFORM_POSIX) diff --git a/src/core/nng_impl.h b/src/core/nng_impl.h index 1f2297c1..bee6ae41 100644 --- a/src/core/nng_impl.h +++ b/src/core/nng_impl.h @@ -44,6 +44,7 @@ #include "core/thread.h" #include "core/timer.h" #include "core/transport.h" +#include "core/url.h" // These have to come after the others - particularly transport.h #include "core/endpt.h" diff --git a/src/core/transport.h b/src/core/transport.h index e8a1f620..3098bd1a 100644 --- a/src/core/transport.h +++ b/src/core/transport.h @@ -13,7 +13,6 @@ // Transport implementation details. Transports must implement the // interfaces in this file. - struct nni_tran { // tran_version is the version of the transport ops that this // transport implements. We only bother to version the main @@ -164,6 +163,7 @@ struct nni_tran_pipe { }; // Utility for transports. + extern int nni_tran_parse_host_port(const char *, char **, char **); // These APIs are used by the framework internally, and not for use by diff --git a/src/core/url.c b/src/core/url.c new file mode 100644 index 00000000..8dee5219 --- /dev/null +++ b/src/core/url.c @@ -0,0 +1,367 @@ +// +// Copyright 2017 Garrett D'Amore <garrett@damore.org> +// Copyright 2017 Capitar IT Group BV <info@capitar.com> +// +// This software is supplied under the terms of the MIT License, a +// copy of which should be located in the distribution where this +// file was obtained (LICENSE.txt). A copy of the license may also be +// found online at https://opensource.org/licenses/MIT. +// + +#include "core/nng_impl.h" + +#include <ctype.h> +#include <stdio.h> +#include <string.h> + +#include "url.h" + +static char +url_hexval(char c) +{ + if ((c >= '0') && (c <= '9')) { + return (c - '0'); + } + if ((c >= 'A') && (c <= 'F')) { + return (c - 'A'); + } + if ((c >= 'a') && (c <= 'f')) { + return (c - 'a'); + } + return (0); +} + +static int +url_decode_buf(const char *in, char *out, int len) +{ + int dlen; + const uint8_t *src; + uint8_t * dst; + int c; + + src = (const uint8_t *) in; + dst = (uint8_t *) out; + + dlen = 0; + while ((c = *src) != 0) { + switch (c) { + case '%': + if ((!isxdigit(src[1])) || (!isxdigit(src[2]))) { + return (-1); + } + c = (url_hexval(src[1]) * 16) + url_hexval(src[2]); + // We don't support encoded control characters. + if ((c < ' ') || (c == 0x7F)) { + return (-1); + } + src += 3; + break; + case '+': + src++; + c = ' '; + break; + default: + // Reject control characters and non-ASCII + if ((c >= 0x7F) || (c <= ' ')) { + return (-1); + } + // Technically this will accept some "unsafe" + // characters as is. + src++; + break; + } + + if (dlen < len) { + *dst++ = c; + } + dlen++; + } + if (dlen < len) { + *dst = '\0'; + } + dlen++; // for null terminator + return (dlen); +} + +int +nni_url_decode(char **out, const char *in) +{ + int len = 0; + char *dst; + + if ((len = url_decode_buf(in, NULL, 0)) < 1) { + return (NNG_EINVAL); + } + if ((dst = nni_alloc(len)) != 0) { + return (NNG_ENOMEM); + } + url_decode_buf(in, dst, len); + *out = dst; + return (0); +} + +static const char *url_hexdigits = "0123456789ABCDEF"; +static const char *url_safe = "-_.~"; + +static int +url_encode_buf(const char *in, char *out, int len, const char *specials) +{ + uint8_t * dst; + const uint8_t *src; + int dlen; + int c; + + dlen = 0; + src = (const uint8_t *) in; + dst = (uint8_t *) out; + + while ((c = *src) != 0) { + if ((c < ' ') || (c == 0x7F)) { + // No encoding of control characters + return (-1); + } + if ((c < 0x80) && + ((isalnum(c) || (strchr(specials, c) != NULL) || + (strchr(url_safe, c) != NULL)))) { + if (dlen < len) { + *dst++ = c; + } + dlen++; + src++; + continue; + } + + if (dlen < len) { + *dst++ = '%'; + } + dlen++; + if (dlen < len) { + *dst++ = url_hexdigits[((c & 0xf0) >> 4)]; + } + dlen++; + if (dlen < len) { + *dst++ = url_hexdigits[(c & 0xf)]; + } + dlen++; + src++; + } + if (dlen < len) { + *dst = '\0'; + } + dlen++; + return (dlen); +} + +int +nni_url_encode_ext(char **out, const char *in, const char *specials) +{ + int len; + char *dst; + + if ((len = url_encode_buf(in, NULL, 0, specials)) < 0) { + return (NNG_EINVAL); + } + if ((dst = nni_alloc(len)) == NULL) { + return (NNG_ENOMEM); + } + url_encode_buf(in, dst, len, specials); + *out = dst; + return (0); +} + +int +nni_url_encode(char **out, const char *in) +{ + return (nni_url_encode_ext(out, in, "")); +} + +// URLs usually follow the following format: +// +// scheme:[//[userinfo@]host][/]path[?query][#fragment] +// +// There are other URL formats, for example mailto: but these are +// generally not used with nanomsg transports. Golang calls these +// +// scheme:opaque[?query][#fragment] +// +// Nanomsg URLs are always of the first form, we always require a +// scheme with a leading //, such as http:// or tcp://. So our parser +// is a bit more restricted, but sufficient for our needs. +int +nni_url_parse(nni_url **urlp, const char *raw) +{ + nni_url * url; + size_t len; + const char *s; + char c; + int rv; + + if ((url = NNI_ALLOC_STRUCT(url)) == NULL) { + return (NNG_ENOMEM); + } + + if ((url->u_rawurl = nni_strdup(raw)) == NULL) { + rv = NNG_ENOMEM; + goto error; + } + + // Grab the scheme. + s = raw; + for (len = 0; (c = s[len]) != ':'; len++) { + if (c == 0) { + break; + } + } + if (strncmp(s + len, "://", 3) != 0) { + rv = NNG_EINVAL; + goto error; + } + + if ((url->u_scheme = nni_alloc(len + 1)) == NULL) { + rv = NNG_ENOMEM; + goto error; + } + memcpy(url->u_scheme, s, len); + url->u_scheme[len] = '\0'; + + // Look for host part (including colon). Will be terminated by + // a path, or NUL. May also include an "@", separating a user + // field. + s += len + 3; // strlen("://") + for (len = 0; (c = s[len]) != '/'; len++) { + if ((c == '\0') || (c == '#') || (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; + } + } + + if ((url->u_host = nni_alloc(len + 1)) == NULL) { + nni_url_free(url); + return (NNG_ENOMEM); + } + memcpy(url->u_host, s, len); + url->u_host[len] = '\0'; + s += len; + + for (len = 0; (c = s[len]) != '\0'; len++) { + if ((c == '?') || (c == '#')) { + break; + } + } + + 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; + len = 0; + + // Look for query info portion. + if (s[0] == '?') { + s++; + for (len = 0; (c = s[len]) != '\0'; len++) { + if (c == '#') { + break; + } + } + 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; + } + } + + // 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') { + rv = NNG_EINVAL; + goto error; + } + } + if ((s[len + 1] != ':') && (s[len + 1] != '\0')) { + rv = NNG_EINVAL; + goto error; + } + } else { + for (len = 0; s[len] != ':'; len++) { + if (s[len] == '\0') { + break; + } + } + } + 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 ((url->u_port = nni_strdup(s + 1)) == NULL) { + rv = NNG_ENOMEM; + goto error; + } + } else if ((url->u_port = nni_strdup("")) == NULL) { + rv = NNG_ENOMEM; + goto error; + } + + *urlp = url; + return (0); + +error: + nni_url_free(url); + return (rv); +} + +void +nni_url_free(nni_url *url) +{ + nni_strfree(url->u_rawurl); + nni_strfree(url->u_scheme); + 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); + NNI_FREE_STRUCT(url); +}
\ No newline at end of file diff --git a/src/core/url.h b/src/core/url.h new file mode 100644 index 00000000..ee336b1d --- /dev/null +++ b/src/core/url.h @@ -0,0 +1,52 @@ +// +// Copyright 2018 Garrett D'Amore <garrett@damore.org> +// Copyright 2018 Capitar IT Group BV <info@capitar.com> +// +// This software is supplied under the terms of the MIT License, a +// copy of which should be located in the distribution where this +// file was obtained (LICENSE.txt). A copy of the license may also be +// found online at https://opensource.org/licenses/MIT. +// + +#ifndef CORE_URL_H +#define CORE_URL_H + +typedef struct nni_url nni_url; + +struct nni_url { + char *u_rawurl; // never NULL + char *u_scheme; // never NULL + 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 + char *u_fragment; // without '#', will be NULL if not specified +}; + +extern int nni_url_parse(nni_url **, const char *path); +extern void nni_url_free(nni_url *); + +// nni_url_decode decodes the string, converting escaped characters to their +// proper form. The newly allocated string is returned in the first argument +// and may be freed with nni_strfree(). Note that we return EINVAL in the +// presence of an encoding of a control character. (Most especially NUL +// would cause problems for C code, but the other control characters have +// no business inside a URL either.) +extern int nni_url_decode(char **, const char *); + +// nni_url_encode works like nni_url_decode, but does the opposite transform. +// "Reserved" special characters (such as "/" and "@") are encoded, so don't +// use this to encode the entire URL.) This is most useful when encoding +// individual components, such as a value for a query parameter. Note that +// this returns NNG_EINVAL if the input string contains control characters, +// as those have no business inside a URL. +extern int nni_url_encode(char **, const char *); + +// nni_url_encode_ext works like nni_url_encode, but passes the named +// special characters. For example, to URL encode all elements in a path +// while preserving director separators, use the string "/" for specials. +extern int nni_url_encode_ext(char **, const char *, const char *); + +#endif // CORE_URL_H diff --git a/src/transport/tls/tls.c b/src/transport/tls/tls.c index 21798c04..31426a78 100644 --- a/src/transport/tls/tls.c +++ b/src/transport/tls/tls.c @@ -26,7 +26,6 @@ typedef struct nni_tls_ep nni_tls_ep; // nni_tls_pipe is one end of a TLS connection. struct nni_tls_pipe { - const char * addr; nni_plat_tcp_pipe *tcp; uint16_t peer; uint16_t proto; @@ -51,7 +50,6 @@ struct nni_tls_pipe { }; struct nni_tls_ep { - char addr[NNG_MAXADDRLEN + 1]; nni_plat_tcp_ep *tep; uint16_t proto; size_t rcvmax; @@ -62,6 +60,7 @@ struct nni_tls_ep { nni_aio * user_aio; nni_mtx mtx; nng_tls_config * cfg; + nni_url * url; }; static void nni_tls_pipe_send_cb(void *); @@ -104,9 +103,7 @@ nni_tls_pipe_fini(void *arg) if (p->tls != NULL) { nni_tls_fini(p->tls); } - if (p->rxmsg) { - nni_msg_free(p->rxmsg); - } + nni_msg_free(p->rxmsg); NNI_FREE_STRUCT(p); } @@ -133,7 +130,6 @@ nni_tls_pipe_init(nni_tls_pipe **pipep, nni_tls_ep *ep, void *tpp) p->proto = ep->proto; p->rcvmax = ep->rcvmax; p->tcp = tcp; - p->addr = ep->addr; *pipep = p; return (0); @@ -498,57 +494,6 @@ nni_tls_pipe_getopt_remaddr(void *arg, void *v, size_t *szp) return (rv); } -// Note that the url *must* be in a modifiable buffer. -int -nni_tls_parse_url(char *url, char **lhost, char **lserv, char **rhost, - char **rserv, int mode) -{ - char *h1; - int rv; - - if (strncmp(url, "tls+tcp://", strlen("tls+tcp://")) != 0) { - return (NNG_EADDRINVAL); - } - url += strlen("tls+tcp://"); - if ((mode == NNI_EP_MODE_DIAL) && ((h1 = strchr(url, ';')) != 0)) { - // The local address is the first part, the remote address - // is the second part. - *h1 = '\0'; - h1++; - if (((rv = nni_tran_parse_host_port(h1, rhost, rserv)) != 0) || - ((rv = nni_tran_parse_host_port(url, lhost, lserv)) != - 0)) { - return (rv); - } - if ((*rserv == NULL) || (*rhost == NULL)) { - // We have to know where to connect to! - return (NNG_EADDRINVAL); - } - } else if (mode == NNI_EP_MODE_DIAL) { - *lhost = NULL; - *lserv = NULL; - if ((rv = nni_tran_parse_host_port(url, rhost, rserv)) != 0) { - return (rv); - } - if ((*rserv == NULL) || (*rhost == NULL)) { - // We have to know where to connect to! - return (NNG_EADDRINVAL); - } - } else { - NNI_ASSERT(mode == NNI_EP_MODE_LISTEN); - *rhost = NULL; - *rserv = NULL; - if ((rv = nni_tran_parse_host_port(url, lhost, lserv)) != 0) { - return (rv); - } - // We have to have a port to listen on! - if (*lserv == NULL) { - return (NNG_EADDRINVAL); - } - } - return (0); -} - static void nni_tls_pipe_start(void *arg, nni_aio *aio) { @@ -592,114 +537,111 @@ nni_tls_ep_fini(void *arg) if (ep->cfg) { nni_tls_config_fini(ep->cfg); } + if (ep->url) { + nni_url_free(ep->url); + } nni_aio_fini(ep->aio); nni_mtx_fini(&ep->mtx); NNI_FREE_STRUCT(ep); } static int -nni_tls_ep_init(void **epp, const char *url, nni_sock *sock, int mode) +nni_tls_ep_init(void **epp, const char *addr, nni_sock *sock, int mode) { nni_tls_ep * ep; int rv; - char buf[NNG_MAXADDRLEN + 1]; - char * rhost; - char * rserv; - char * lhost; - char * lserv; + char * host; + char * serv; nni_sockaddr rsa, lsa; nni_aio * aio; int passive; nng_tls_mode tlsmode; nng_tls_auth_mode authmode; + nni_url * url; + + // Parse the URLs first. + if ((rv = nni_url_parse(&url, addr)) != 0) { + return (rv); + } - // Make a copy of the url (to allow for destructive operations) - if (nni_strlcpy(buf, url, sizeof(buf)) >= sizeof(buf)) { + // Check for invalid URL components. + if ((strlen(url->u_path) != 0) && (strcmp(url->u_path, "/") != 0)) { + nni_url_free(url); + return (NNG_EADDRINVAL); + } + if ((url->u_fragment != NULL) || (url->u_userinfo != NULL) || + (url->u_query != NULL)) { + nni_url_free(url); return (NNG_EADDRINVAL); } if ((rv = nni_aio_init(&aio, NULL, NULL)) != 0) { + nni_url_free(url); return (rv); } - // Parse the URLs first. - rv = nni_tls_parse_url(buf, &lhost, &lserv, &rhost, &rserv, mode); - if (rv != 0) { - nni_aio_fini(aio); - return (rv); + + if ((strlen(url->u_hostname) == 0) || + (strcmp(url->u_hostname, "*") == 0)) { + host = NULL; + } else { + host = url->u_hostname; } - if (mode == NNI_EP_MODE_DIAL) { - passive = 0; - tlsmode = NNG_TLS_MODE_CLIENT; - authmode = NNG_TLS_AUTH_MODE_REQUIRED; + if (strlen(url->u_port) == 0) { + serv = NULL; } else { - passive = 1; - tlsmode = NNG_TLS_MODE_SERVER; - authmode = NNG_TLS_AUTH_MODE_NONE; + serv = url->u_port; } - // XXX: arguably we could defer this part to the point we do a bind - // or connect! - - if ((rhost != NULL) || (rserv != NULL)) { - aio->a_addr = &rsa; - nni_plat_tcp_resolv(rhost, rserv, NNG_AF_UNSPEC, passive, aio); - nni_aio_wait(aio); - nni_strfree(rserv); - if ((rv = nni_aio_result(aio)) != 0) { - nni_strfree(rhost); - nni_strfree(lhost); - nni_strfree(lserv); + if (mode == NNI_EP_MODE_DIAL) { + passive = 0; + tlsmode = NNG_TLS_MODE_CLIENT; + authmode = NNG_TLS_AUTH_MODE_REQUIRED; + lsa.s_un.s_family = NNG_AF_UNSPEC; + aio->a_addr = &rsa; + if ((host == NULL) || (serv == NULL)) { + nni_url_free(url); nni_aio_fini(aio); - return (rv); + return (NNG_EADDRINVAL); } } else { + passive = 1; + tlsmode = NNG_TLS_MODE_SERVER; + authmode = NNG_TLS_AUTH_MODE_NONE; rsa.s_un.s_family = NNG_AF_UNSPEC; + aio->a_addr = &lsa; } - if ((lhost != NULL) || (lserv != NULL)) { - aio->a_addr = &lsa; - nni_plat_tcp_resolv(lhost, lserv, NNG_AF_UNSPEC, passive, aio); - nni_aio_wait(aio); - nni_strfree(lhost); - nni_strfree(lserv); - if ((rv = nni_aio_result(aio)) != 0) { - nni_strfree(rhost); - nni_aio_fini(aio); - return (rv); - } - } else { - lsa.s_un.s_family = NNG_AF_UNSPEC; + // XXX: arguably we could defer this part to the point we do a bind + // or connect! + nni_plat_tcp_resolv(host, serv, NNG_AF_UNSPEC, passive, aio); + nni_aio_wait(aio); + if ((rv = nni_aio_result(aio)) != 0) { + nni_url_free(url); + nni_aio_fini(aio); + return (rv); } nni_aio_fini(aio); if ((ep = NNI_ALLOC_STRUCT(ep)) == NULL) { - nni_strfree(rhost); + nni_url_free(url); return (NNG_ENOMEM); } nni_mtx_init(&ep->mtx); - - if (nni_strlcpy(ep->addr, url, sizeof(ep->addr)) >= sizeof(ep->addr)) { - NNI_FREE_STRUCT(ep); - nni_strfree(rhost); - return (NNG_EADDRINVAL); - } + ep->url = url; if (((rv = nni_plat_tcp_ep_init(&ep->tep, &lsa, &rsa, mode)) != 0) || ((rv = nni_tls_config_init(&ep->cfg, tlsmode)) != 0) || ((rv = nng_tls_config_auth_mode(ep->cfg, authmode)) != 0) || ((rv = nni_aio_init(&ep->aio, nni_tls_ep_cb, ep)) != 0)) { - nni_strfree(rhost); nni_tls_ep_fini(ep); return (rv); } - if ((tlsmode == NNG_TLS_MODE_CLIENT) && (rhost != NULL)) { - if ((rv = nng_tls_config_server_name(ep->cfg, rhost)) != 0) { - nni_strfree(rhost); + if ((tlsmode == NNG_TLS_MODE_CLIENT) && (host != NULL)) { + if ((rv = nng_tls_config_server_name(ep->cfg, host)) != 0) { nni_tls_ep_fini(ep); return (rv); } } - nni_strfree(rhost); ep->proto = nni_sock_proto(sock); ep->authmode = authmode; |
