aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGarrett D'Amore <garrett@damore.org>2018-01-04 20:11:38 -0800
committerGarrett D'Amore <garrett@damore.org>2018-01-05 11:20:50 -0800
commit224dae56a379aa309fca261d61e7e356b14a536f (patch)
tree8194d33029d3595457d424a0f2f57fe2d2139199
parent2ea7ae1ae5755ab72833fdea0dcf8e13e4d91d0d (diff)
downloadnng-224dae56a379aa309fca261d61e7e356b14a536f.tar.gz
nng-224dae56a379aa309fca261d61e7e356b14a536f.tar.bz2
nng-224dae56a379aa309fca261d61e7e356b14a536f.zip
Fix some more leaks, add a generic URL parser.
-rw-r--r--src/CMakeLists.txt2
-rw-r--r--src/core/nng_impl.h1
-rw-r--r--src/core/transport.h2
-rw-r--r--src/core/url.c367
-rw-r--r--src/core/url.h52
-rw-r--r--src/transport/tls/tls.c172
-rw-r--r--tests/CMakeLists.txt1
-rw-r--r--tests/tls.c52
-rw-r--r--tests/trantest.h4
-rw-r--r--tests/url.c230
-rw-r--r--tests/ws.c101
-rw-r--r--tests/wss.c101
12 files changed, 829 insertions, 256 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;
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 7e855f23..f6c9a62d 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -158,6 +158,7 @@ add_nng_test(tcp 5 NNG_TRANSPORT_TCP)
add_nng_test(tcp6 5 NNG_TRANSPORT_TCP)
add_nng_test(transport 5 ON)
add_nng_test(udp 5 ON)
+add_nng_test(url 5 ON)
add_nng_test(ws 30 NNG_TRANSPORT_WS)
add_nng_test(wss 30 NNG_TRANSPORT_WSS)
add_nng_test(zt 60 NNG_TRANSPORT_ZEROTIER)
diff --git a/tests/tls.c b/tests/tls.c
index fe8aea1e..4ce70a28 100644
--- a/tests/tls.c
+++ b/tests/tls.c
@@ -1,7 +1,6 @@
//
-// Copyright 2017 Garrett D'Amore <garrett@damore.org>
-// Copyright 2017 Capitar IT Group BV <info@capitar.com>
-// Copyright 2017 Staysail Systems, Inc. <info@staysail.tech>
+// Copyright 2018 Capitar IT Group BV <info@capitar.com>
+// Copyright 2018 Staysail Systems, Inc. <info@staysail.tech>
//
// This software is supplied under the terms of the MIT License, a
// copy of which should be located in the distribution where this
@@ -79,27 +78,23 @@ check_props_v4(nng_msg *msg, nng_listener l, nng_dialer d)
size_t z;
p = nng_msg_get_pipe(msg);
So(p > 0);
-
- Convey("Local address property works", {
- nng_sockaddr la;
- z = sizeof(nng_sockaddr);
- So(nng_pipe_getopt(p, NNG_OPT_LOCADDR, &la, &z) == 0);
- So(z == sizeof(la));
- So(la.s_un.s_family == NNG_AF_INET);
- So(la.s_un.s_in.sa_port == htons(trantest_port - 1));
- So(la.s_un.s_in.sa_port != 0);
- So(la.s_un.s_in.sa_addr == htonl(0x7f000001));
- });
-
- Convey("Remote address property works", {
- nng_sockaddr ra;
- z = sizeof(nng_sockaddr);
- So(nng_pipe_getopt(p, NNG_OPT_REMADDR, &ra, &z) == 0);
- So(z == sizeof(ra));
- So(ra.s_un.s_family == NNG_AF_INET);
- So(ra.s_un.s_in.sa_port != 0);
- So(ra.s_un.s_in.sa_addr == htonl(0x7f000001));
- });
+ nng_sockaddr la;
+ nng_sockaddr ra;
+
+ z = sizeof(nng_sockaddr);
+ So(nng_pipe_getopt(p, NNG_OPT_LOCADDR, &la, &z) == 0);
+ So(z == sizeof(la));
+ So(la.s_un.s_family == NNG_AF_INET);
+ So(la.s_un.s_in.sa_port == htons(trantest_port - 1));
+ So(la.s_un.s_in.sa_port != 0);
+ So(la.s_un.s_in.sa_addr == htonl(0x7f000001));
+
+ z = sizeof(nng_sockaddr);
+ So(nng_pipe_getopt(p, NNG_OPT_REMADDR, &ra, &z) == 0);
+ So(z == sizeof(ra));
+ So(ra.s_un.s_family == NNG_AF_INET);
+ So(ra.s_un.s_in.sa_port != 0);
+ So(ra.s_un.s_in.sa_addr == htonl(0x7f000001));
return (0);
}
@@ -205,18 +200,21 @@ TestMain("TLS Transport", {
So(nng_tls_register() == 0);
So(nng_pair_open(&s1) == 0);
Reset({ nng_close(s1); });
+
+ // Note that if we listen to an unspecified port, then we
+ // get a random port. So we don't look at that. This allows
+ // a user to obtain a port at random and then query to see
+ // which one was chosen.
+
So(nng_dial(s1, "tls+tcp://127.0.0.1", NULL, 0) ==
NNG_EADDRINVAL);
So(nng_dial(s1, "tls+tcp://127.0.0.1.32", NULL, 0) ==
NNG_EADDRINVAL);
So(nng_dial(s1, "tls+tcp://127.0.x.1.32", NULL, 0) ==
NNG_EADDRINVAL);
- So(nng_listen(s1, "tls+tcp://127.0.0.1", NULL, 0) ==
- NNG_EADDRINVAL);
So(nng_listen(s1, "tls+tcp://127.0.0.1.32", NULL, 0) ==
NNG_EADDRINVAL);
So(nng_listen(s1, "tls+tcp://127.0.x.1.32", NULL, 0) ==
NNG_EADDRINVAL);
});
-
})
diff --git a/tests/trantest.h b/tests/trantest.h
index 0346e3ff..46eaa7b1 100644
--- a/tests/trantest.h
+++ b/tests/trantest.h
@@ -248,10 +248,11 @@ trantest_listen_accept(trantest *tt)
So(trantest_listen(tt, &l) == 0);
So(l != 0);
+ nng_msleep(200);
d = 0;
So(trantest_dial(tt, &d) == 0);
So(d != 0);
- })
+ });
}
void
@@ -384,6 +385,7 @@ trantest_check_properties(trantest *tt, trantest_proptest_t f)
So(nng_msg_append(send, "props", 5) == 0);
So(nng_sendmsg(tt->reqsock, send, 0) == 0);
+
recv = NULL;
So(nng_recvmsg(tt->repsock, &recv, 0) == 0);
So(recv != NULL);
diff --git a/tests/url.c b/tests/url.c
new file mode 100644
index 00000000..019a1519
--- /dev/null
+++ b/tests/url.c
@@ -0,0 +1,230 @@
+//
+// Copyright 2018 Staysail Systems, Inc. <info@staysail.tech>
+// 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.
+//
+
+#include <string.h>
+
+#include "convey.h"
+
+#include "core/nng_impl.h"
+#include "core/url.h"
+
+//#include "stubs.h"
+
+TestMain("URLs", {
+
+ nni_url *url;
+
+ Convey("http://www.google.com", {
+ So(nni_url_parse(&url, "http://www.google.com") == 0);
+ So(url != NULL);
+ So(strcmp(url->u_scheme, "http") == 0);
+ So(strcmp(url->u_host, "www.google.com") == 0);
+ So(strcmp(url->u_hostname, "www.google.com") == 0);
+ So(strcmp(url->u_port, "") == 0);
+ So(strcmp(url->u_path, "") == 0);
+ So(url->u_query == NULL);
+ So(url->u_fragment == NULL);
+ So(url->u_userinfo == NULL);
+ nni_url_free(url);
+ });
+
+ Convey("http://www.google.com:1234", {
+ So(nni_url_parse(&url, "http://www.google.com:1234") == 0);
+ So(url != NULL);
+ So(strcmp(url->u_scheme, "http") == 0);
+ So(strcmp(url->u_host, "www.google.com:1234") == 0);
+ So(strcmp(url->u_hostname, "www.google.com") == 0);
+ So(strcmp(url->u_port, "1234") == 0);
+ So(strcmp(url->u_path, "") == 0);
+ So(url->u_query == NULL);
+ So(url->u_fragment == NULL);
+ So(url->u_userinfo == NULL);
+ nni_url_free(url);
+ });
+
+ Convey("http://www.google.com:1234/somewhere", {
+ So(nni_url_parse(
+ &url, "http://www.google.com:1234/somewhere") == 0);
+ So(url != NULL);
+ So(strcmp(url->u_scheme, "http") == 0);
+ So(strcmp(url->u_host, "www.google.com:1234") == 0);
+ So(strcmp(url->u_hostname, "www.google.com") == 0);
+ So(strcmp(url->u_port, "1234") == 0);
+ So(strcmp(url->u_path, "/somewhere") == 0);
+ So(url->u_userinfo == NULL);
+ So(url->u_query == NULL);
+ So(url->u_fragment == NULL);
+ nni_url_free(url);
+ });
+ Convey("http://garrett@www.google.com:1234/somewhere", {
+ So(nni_url_parse(&url,
+ "http://garrett@www.google.com:1234/somewhere") == 0);
+ So(url != NULL);
+ So(strcmp(url->u_scheme, "http") == 0);
+ So(strcmp(url->u_userinfo, "garrett") == 0);
+ So(strcmp(url->u_host, "www.google.com:1234") == 0);
+ So(strcmp(url->u_hostname, "www.google.com") == 0);
+ So(strcmp(url->u_port, "1234") == 0);
+ So(strcmp(url->u_path, "/somewhere") == 0);
+ So(url->u_query == NULL);
+ So(url->u_fragment == NULL);
+ nni_url_free(url);
+ });
+ Convey("http://www.google.com/somewhere?result=yes", {
+ So(nni_url_parse(&url,
+ "http://www.google.com/somewhere?result=yes") == 0);
+ So(url != NULL);
+ So(strcmp(url->u_scheme, "http") == 0);
+ So(strcmp(url->u_host, "www.google.com") == 0);
+ So(strcmp(url->u_hostname, "www.google.com") == 0);
+ So(strcmp(url->u_port, "") == 0);
+ So(strcmp(url->u_path, "/somewhere") == 0);
+ So(strcmp(url->u_query, "result=yes") == 0);
+ So(url->u_userinfo == NULL);
+ So(url->u_fragment == NULL);
+ nni_url_free(url);
+ });
+ Convey("http://www.google.com/somewhere?result=yes#chapter1", {
+ So(nni_url_parse(&url,
+ "http://www.google.com/"
+ "somewhere?result=yes#chapter1") == 0);
+ So(url != NULL);
+ So(strcmp(url->u_scheme, "http") == 0);
+ So(strcmp(url->u_host, "www.google.com") == 0);
+ So(strcmp(url->u_hostname, "www.google.com") == 0);
+ So(strcmp(url->u_port, "") == 0);
+ So(strcmp(url->u_path, "/somewhere") == 0);
+ So(strcmp(url->u_query, "result=yes") == 0);
+ So(strcmp(url->u_fragment, "chapter1") == 0);
+ So(url->u_userinfo == NULL);
+ nni_url_free(url);
+ });
+ Convey("http://www.google.com/somewhere#chapter2", {
+ So(nni_url_parse(
+ &url, "http://www.google.com/somewhere#chapter2") == 0);
+ So(url != NULL);
+ So(strcmp(url->u_scheme, "http") == 0);
+ So(strcmp(url->u_host, "www.google.com") == 0);
+ So(strcmp(url->u_hostname, "www.google.com") == 0);
+ So(strcmp(url->u_port, "") == 0);
+ So(strcmp(url->u_path, "/somewhere") == 0);
+ So(strcmp(url->u_fragment, "chapter2") == 0);
+ So(url->u_query == NULL);
+ So(url->u_userinfo == NULL);
+ nni_url_free(url);
+ });
+ Convey("http://www.google.com#chapter3", {
+ So(nni_url_parse(&url, "http://www.google.com#chapter3") == 0);
+ So(url != NULL);
+ So(strcmp(url->u_scheme, "http") == 0);
+ So(strcmp(url->u_host, "www.google.com") == 0);
+ So(strcmp(url->u_hostname, "www.google.com") == 0);
+ So(strcmp(url->u_path, "") == 0);
+ So(strcmp(url->u_port, "") == 0);
+ So(strcmp(url->u_fragment, "chapter3") == 0);
+ So(url->u_query == NULL);
+ So(url->u_userinfo == NULL);
+ nni_url_free(url);
+ });
+ Convey("http://www.google.com?color=red", {
+ So(nni_url_parse(&url, "http://www.google.com?color=red") ==
+ 0);
+ So(url != NULL);
+ So(strcmp(url->u_scheme, "http") == 0);
+ So(strcmp(url->u_host, "www.google.com") == 0);
+ So(strcmp(url->u_hostname, "www.google.com") == 0);
+ So(strcmp(url->u_path, "") == 0);
+ So(strcmp(url->u_port, "") == 0);
+ So(strcmp(url->u_query, "color=red") == 0);
+ So(url->u_fragment == NULL);
+ So(url->u_userinfo == NULL);
+ nni_url_free(url);
+ });
+
+ Convey("http://[::1]", {
+ So(nni_url_parse(&url, "http://[::1]") == 0);
+ So(url != NULL);
+ So(strcmp(url->u_scheme, "http") == 0);
+ So(strcmp(url->u_host, "[::1]") == 0);
+ So(strcmp(url->u_hostname, "::1") == 0);
+ So(strcmp(url->u_path, "") == 0);
+ So(strcmp(url->u_port, "") == 0);
+ So(url->u_query == NULL);
+ So(url->u_fragment == NULL);
+ So(url->u_userinfo == NULL);
+ nni_url_free(url);
+ });
+
+ Convey("http://[::1]:29", {
+ So(nni_url_parse(&url, "http://[::1]:29") == 0);
+ So(url != NULL);
+ So(strcmp(url->u_scheme, "http") == 0);
+ So(strcmp(url->u_host, "[::1]:29") == 0);
+ So(strcmp(url->u_hostname, "::1") == 0);
+ So(strcmp(url->u_path, "") == 0);
+ So(strcmp(url->u_port, "29") == 0);
+ So(url->u_query == NULL);
+ So(url->u_fragment == NULL);
+ So(url->u_userinfo == NULL);
+ nni_url_free(url);
+ });
+ Convey("http://[::1]:29/bottles", {
+ So(nni_url_parse(&url, "http://[::1]:29/bottles") == 0);
+ So(url != NULL);
+ So(strcmp(url->u_scheme, "http") == 0);
+ So(strcmp(url->u_host, "[::1]:29") == 0);
+ So(strcmp(url->u_hostname, "::1") == 0);
+ So(strcmp(url->u_path, "/bottles") == 0);
+ So(strcmp(url->u_port, "29") == 0);
+ So(url->u_query == NULL);
+ So(url->u_fragment == NULL);
+ So(url->u_userinfo == NULL);
+ nni_url_free(url);
+ });
+
+ Convey("tcp://:9876/", {
+ So(nni_url_parse(&url, "tcp://:9876/") == 0);
+ So(url != NULL);
+ So(strcmp(url->u_scheme, "tcp") == 0);
+ So(strcmp(url->u_host, ":9876") == 0);
+ So(strcmp(url->u_hostname, "") == 0);
+ So(strcmp(url->u_path, "/") == 0);
+ So(strcmp(url->u_port, "9876") == 0);
+ So(url->u_query == NULL);
+ So(url->u_fragment == NULL);
+ So(url->u_userinfo == NULL);
+ nni_url_free(url);
+ });
+
+ Convey("Negative www.google.com", {
+ url = NULL;
+ So(nni_url_parse(&url, "www.google.com") == NNG_EINVAL);
+ So(url == NULL);
+ });
+
+ Convey("Negative http:www.google.com", {
+ url = NULL;
+ So(nni_url_parse(&url, "http:www.google.com") == NNG_EINVAL);
+ So(url == NULL);
+ });
+
+ Convey("Negative http://[::1", {
+ url = NULL;
+ So(nni_url_parse(&url, "http://[::1") == NNG_EINVAL);
+ So(url == NULL);
+ });
+
+ Convey("Negative http://[::1]bogus", {
+ url = NULL;
+ So(nni_url_parse(&url, "http://[::1]bogus") == NNG_EINVAL);
+ So(url == NULL);
+ });
+
+})
diff --git a/tests/ws.c b/tests/ws.c
index aa2ba56e..386c0690 100644
--- a/tests/ws.c
+++ b/tests/ws.c
@@ -24,65 +24,54 @@
static int
check_props_v4(nng_msg *msg, nng_listener l, nng_dialer d)
{
- nng_pipe p;
- size_t z;
+ nng_pipe p;
+ size_t z;
+ nng_sockaddr la;
+ nng_sockaddr ra;
+ char * buf;
+ size_t len;
+
p = nng_msg_get_pipe(msg);
So(p > 0);
- Convey("Local address property works", {
- nng_sockaddr la;
- z = sizeof(nng_sockaddr);
- So(nng_pipe_getopt(p, NNG_OPT_LOCADDR, &la, &z) == 0);
- So(z == sizeof(la));
- So(la.s_un.s_family == NNG_AF_INET);
- So(la.s_un.s_in.sa_port == htons(trantest_port - 1));
- So(la.s_un.s_in.sa_port != 0);
- So(la.s_un.s_in.sa_addr == htonl(0x7f000001));
- });
-
- Convey("Remote address property works", {
- nng_sockaddr ra;
- z = sizeof(nng_sockaddr);
- So(nng_pipe_getopt(p, NNG_OPT_REMADDR, &ra, &z) == 0);
- So(z == sizeof(ra));
- So(ra.s_un.s_family == NNG_AF_INET);
- So(ra.s_un.s_in.sa_port != 0);
- So(ra.s_un.s_in.sa_addr == htonl(0x7f000001));
- });
-
- Convey("Request header property works", {
- char * buf;
- size_t len;
- z = 0;
- buf = NULL;
- So(nng_pipe_getopt(p, NNG_OPT_WS_REQUEST_HEADERS, buf, &z) ==
- 0);
- So(z > 0);
- len = z;
- So((buf = nni_alloc(len)) != NULL);
- So(nng_pipe_getopt(p, NNG_OPT_WS_REQUEST_HEADERS, buf, &z) ==
- 0);
- So(strstr(buf, "Sec-WebSocket-Key") != NULL);
- So(z == len);
- nni_free(buf, len);
- });
-
- Convey("Response header property works", {
- char * buf;
- size_t len;
- z = 0;
- buf = NULL;
- So(nng_pipe_getopt(p, NNG_OPT_WS_RESPONSE_HEADERS, buf, &z) ==
- 0);
- So(z > 0);
- len = z;
- So((buf = nni_alloc(len)) != NULL);
- So(nng_pipe_getopt(p, NNG_OPT_WS_RESPONSE_HEADERS, buf, &z) ==
- 0);
- So(strstr(buf, "Sec-WebSocket-Accept") != NULL);
- So(z == len);
- nni_free(buf, len);
- });
+ z = sizeof(nng_sockaddr);
+ So(nng_pipe_getopt(p, NNG_OPT_LOCADDR, &la, &z) == 0);
+ So(z == sizeof(la));
+ So(la.s_un.s_family == NNG_AF_INET);
+ So(la.s_un.s_in.sa_port == htons(trantest_port - 1));
+ So(la.s_un.s_in.sa_port != 0);
+ So(la.s_un.s_in.sa_addr == htonl(0x7f000001));
+
+ z = sizeof(nng_sockaddr);
+ So(nng_pipe_getopt(p, NNG_OPT_REMADDR, &ra, &z) == 0);
+ So(z == sizeof(ra));
+ So(ra.s_un.s_family == NNG_AF_INET);
+ So(ra.s_un.s_in.sa_port != 0);
+ So(ra.s_un.s_in.sa_addr == htonl(0x7f000001));
+
+ // Request Header
+ z = 0;
+ buf = NULL;
+ So(nng_pipe_getopt(p, NNG_OPT_WS_REQUEST_HEADERS, buf, &z) == 0);
+ So(z > 0);
+ len = z;
+ So((buf = nni_alloc(len)) != NULL);
+ So(nng_pipe_getopt(p, NNG_OPT_WS_REQUEST_HEADERS, buf, &z) == 0);
+ So(strstr(buf, "Sec-WebSocket-Key") != NULL);
+ So(z == len);
+ nni_free(buf, len);
+
+ // Response Header
+ z = 0;
+ buf = NULL;
+ So(nng_pipe_getopt(p, NNG_OPT_WS_RESPONSE_HEADERS, buf, &z) == 0);
+ So(z > 0);
+ len = z;
+ So((buf = nni_alloc(len)) != NULL);
+ So(nng_pipe_getopt(p, NNG_OPT_WS_RESPONSE_HEADERS, buf, &z) == 0);
+ So(strstr(buf, "Sec-WebSocket-Accept") != NULL);
+ So(z == len);
+ nni_free(buf, len);
return (0);
}
diff --git a/tests/wss.c b/tests/wss.c
index 151b4287..38b333b9 100644
--- a/tests/wss.c
+++ b/tests/wss.c
@@ -72,65 +72,54 @@ static const char server_key[] =
static int
check_props_v4(nng_msg *msg, nng_listener l, nng_dialer d)
{
- nng_pipe p;
- size_t z;
+ nng_pipe p;
+ size_t z;
+ nng_sockaddr la;
+ nng_sockaddr ra;
+ char * buf;
+ size_t len;
+
p = nng_msg_get_pipe(msg);
So(p > 0);
- Convey("Local address property works", {
- nng_sockaddr la;
- z = sizeof(nng_sockaddr);
- So(nng_pipe_getopt(p, NNG_OPT_LOCADDR, &la, &z) == 0);
- So(z == sizeof(la));
- So(la.s_un.s_family == NNG_AF_INET);
- So(la.s_un.s_in.sa_port == htons(trantest_port - 1));
- So(la.s_un.s_in.sa_port != 0);
- So(la.s_un.s_in.sa_addr == htonl(0x7f000001));
- });
-
- Convey("Remote address property works", {
- nng_sockaddr ra;
- z = sizeof(nng_sockaddr);
- So(nng_pipe_getopt(p, NNG_OPT_REMADDR, &ra, &z) == 0);
- So(z == sizeof(ra));
- So(ra.s_un.s_family == NNG_AF_INET);
- So(ra.s_un.s_in.sa_port != 0);
- So(ra.s_un.s_in.sa_addr == htonl(0x7f000001));
- });
-
- Convey("Request header property works", {
- char * buf;
- size_t len;
- z = 0;
- buf = NULL;
- So(nng_pipe_getopt(p, NNG_OPT_WS_REQUEST_HEADERS, buf, &z) ==
- 0);
- So(z > 0);
- len = z;
- So((buf = nni_alloc(len)) != NULL);
- So(nng_pipe_getopt(p, NNG_OPT_WS_REQUEST_HEADERS, buf, &z) ==
- 0);
- So(strstr(buf, "Sec-WebSocket-Key") != NULL);
- So(z == len);
- nni_free(buf, len);
- });
-
- Convey("Response header property works", {
- char * buf;
- size_t len;
- z = 0;
- buf = NULL;
- So(nng_pipe_getopt(p, NNG_OPT_WS_RESPONSE_HEADERS, buf, &z) ==
- 0);
- So(z > 0);
- len = z;
- So((buf = nni_alloc(len)) != NULL);
- So(nng_pipe_getopt(p, NNG_OPT_WS_RESPONSE_HEADERS, buf, &z) ==
- 0);
- So(strstr(buf, "Sec-WebSocket-Accept") != NULL);
- So(z == len);
- nni_free(buf, len);
- });
+ z = sizeof(nng_sockaddr);
+ So(nng_pipe_getopt(p, NNG_OPT_LOCADDR, &la, &z) == 0);
+ So(z == sizeof(la));
+ So(la.s_un.s_family == NNG_AF_INET);
+ So(la.s_un.s_in.sa_port == htons(trantest_port - 1));
+ So(la.s_un.s_in.sa_port != 0);
+ So(la.s_un.s_in.sa_addr == htonl(0x7f000001));
+
+ z = sizeof(nng_sockaddr);
+ So(nng_pipe_getopt(p, NNG_OPT_REMADDR, &ra, &z) == 0);
+ So(z == sizeof(ra));
+ So(ra.s_un.s_family == NNG_AF_INET);
+ So(ra.s_un.s_in.sa_port != 0);
+ So(ra.s_un.s_in.sa_addr == htonl(0x7f000001));
+
+ // Request header
+ z = 0;
+ buf = NULL;
+ So(nng_pipe_getopt(p, NNG_OPT_WS_REQUEST_HEADERS, buf, &z) == 0);
+ So(z > 0);
+ len = z;
+ So((buf = nni_alloc(len)) != NULL);
+ So(nng_pipe_getopt(p, NNG_OPT_WS_REQUEST_HEADERS, buf, &z) == 0);
+ So(strstr(buf, "Sec-WebSocket-Key") != NULL);
+ So(z == len);
+ nni_free(buf, len);
+
+ // Response header
+ z = 0;
+ buf = NULL;
+ So(nng_pipe_getopt(p, NNG_OPT_WS_RESPONSE_HEADERS, buf, &z) == 0);
+ So(z > 0);
+ len = z;
+ So((buf = nni_alloc(len)) != NULL);
+ So(nng_pipe_getopt(p, NNG_OPT_WS_RESPONSE_HEADERS, buf, &z) == 0);
+ So(strstr(buf, "Sec-WebSocket-Accept") != NULL);
+ So(z == len);
+ nni_free(buf, len);
return (0);
}