From ced5170d6532f427f6750eee274288ceaffe05b5 Mon Sep 17 00:00:00 2001 From: Garrett D'Amore Date: Tue, 23 Jan 2018 22:03:06 -0800 Subject: fixes #222 Public URL API --- docs/libnng.adoc | 11 +++++ docs/nng_url_clone.adoc | 53 +++++++++++++++++++++++ docs/nng_url_free.adoc | 51 ++++++++++++++++++++++ docs/nng_url_parse.adoc | 111 ++++++++++++++++++++++++++++++++++++++++++++++++ src/core/defs.h | 2 +- src/nng.c | 18 ++++++++ src/nng.h | 29 +++++++++++++ tests/url.c | 95 ++++++++++++++++++++--------------------- 8 files changed, 320 insertions(+), 50 deletions(-) create mode 100644 docs/nng_url_clone.adoc create mode 100644 docs/nng_url_free.adoc create mode 100644 docs/nng_url_parse.adoc diff --git a/docs/libnng.adoc b/docs/libnng.adoc index 179b9a0e..57337f47 100644 --- a/docs/libnng.adoc +++ b/docs/libnng.adoc @@ -160,6 +160,17 @@ The following functions are used to register a transport for use. | <>|register ZeroTier transport |=== +=== URL Object + +Common functionality is supplied for parsing and handling +universal resource locators (URLS). + +|=== +| <>|clone URL structure +| <>|free URL structure +| <>|create URL structure from string +|=== + === TLS Configuration Objects The following functions are used to manipulate transport layer security diff --git a/docs/nng_url_clone.adoc b/docs/nng_url_clone.adoc new file mode 100644 index 00000000..b1714962 --- /dev/null +++ b/docs/nng_url_clone.adoc @@ -0,0 +1,53 @@ += nng_url_clone(3) +:doctype: manpage +:manmanual: nng +:mansource: nng +:manvolnum: 3 +:copyright: Copyright 2018 Staysail Systems, Inc. \ + Copyright 2018 Capitar IT Group BV \ + 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. + +== NAME + +nng_url_clone - clone URL structure + +== SYNOPSIS + +[source, c] +----------- +#include + +int nng_url_clone(nng_url **dup, nng_url *orig); +----------- + +== DESCRIPTION + +The `nng_url_clone()` makes a clone of the original URL structure _orig_, and +saves the result in the location pointed by _dup_. This clone includes +fully duplicating each of the member fields. + +== RETURN VALUES + +This function returns 0 on success, and non-zero otherwise. + +== ERRORS + +`NNG_ENOMEM`:: Insufficient free memory exists to duplicate a message. + +== SEE ALSO + +<>, +<>, +<>, +<> + +== COPYRIGHT + +Copyright 2018 mailto:info@staysail.tech[Staysail Systems, Inc.] + +Copyright 2018 mailto:info@capitar.com[Capitar IT Group BV] + +This document is supplied under the terms of the +https://opensource.org/licenses/MIT[MIT License]. diff --git a/docs/nng_url_free.adoc b/docs/nng_url_free.adoc new file mode 100644 index 00000000..1aa522f0 --- /dev/null +++ b/docs/nng_url_free.adoc @@ -0,0 +1,51 @@ += nng_url_free(3) +:doctype: manpage +:manmanual: nng +:mansource: nng +:manvolnum: 3 +:copyright: Copyright 2018 Staysail Systems, Inc. \ + Copyright 2018 Capitar IT Group BV \ + 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. + +== NAME + +nng_url_free - free a URL structure + +== SYNOPSIS + +[source, c] +----------- +#include + +void nng_url_free(nng_url *url); +----------- + +== DESCRIPTION + +The `nng_url_free()` function deallocates the _url_ entirely, including +any of it's members. + +== RETURN VALUES + +None. + +== ERRORS + +None. + +== SEE ALSO + +<>, +<>, +<> + +== COPYRIGHT + +Copyright 2018 mailto:info@staysail.tech[Staysail Systems, Inc.] + +Copyright 2018 mailto:info@capitar.com[Capitar IT Group BV] + +This document is supplied under the terms of the +https://opensource.org/licenses/MIT[MIT License]. diff --git a/docs/nng_url_parse.adoc b/docs/nng_url_parse.adoc new file mode 100644 index 00000000..0ea0cd1d --- /dev/null +++ b/docs/nng_url_parse.adoc @@ -0,0 +1,111 @@ += nng_url_parse(3) +:doctype: manpage +:manmanual: nng +:mansource: nng +:manvolnum: 3 +:copyright: Copyright 2018 Staysail Systems, Inc. \ + Copyright 2018 Capitar IT Group BV \ + 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. + +== NAME + +nng_url_parse - create URL structure from a string + +== SYNOPSIS + +[source, c] +----------- +#include + +int nng_url_parse(nng_url **urlp, const char *str); +----------- + + +== DESCRIPTION + +The `nng_url_parse()` function parses the string _str_ containing an +https://tools.ietf.org/html/rfc3986[RFC 3986] compliant URL, and creates +a structure containing the results. A pointer to the resulting structure +is stored in _urlp_. + +The `nng_url` structure has at least the following members: + +`char *u_scheme`:: The scheme, such as `http`. Always lower case. + +`char *u_rawurl`:: An unparsed form of the raw URL, with only minimal + canonicalization performed. + +`char *u_userinfo`:: The userinfo component if one was present, + `NULL` otherwise. + +`char *u_host`:: The full host, including hostname, and colon and port + if present, otehrwise the empty string. + +`char *u_hostname`:: The hostname if present, otherwise the empty string. + Always lower case. + +`char *u_port`:: The port if present. If not present, a default port + will be stored here. If no default is available, then + the empty string. + +`char *u_path`:: The path component if present, or the empty string + otherwise. + +`char *u_query`:: The query component if present, NULL otherwise. + +`char *u_fragment`:: The fragment if present, NULL otherwise. + +=== URL Canonicalization + +The `nng_url_parse()` function also canonicalizes the results, as +follows: + + 1. The URL is parsed into the various components. + 2. The `u_scheme`, `u_hostname`, `u_host`, and `u_port` members are + converted to lower case. + 3. Percent-encoded values for + https://tools.ietf.org/html/rfc3986#section-2.3[unreserved characters] + converted to their unencoded forms. + 4. Additionally URL percent-encoded values for characters in the path + and with numeric values larger than 127 (i.e. not ASCII) are decoded. + 5. The resulting `u_path` is checked for invalid UTF-8 sequences, consisting + of surrogate pairs, illegal byte sequences, or overlong encodings. + If this check fails, then the entire URL is considered invalid, and + the function returns `NNG_EINVAL`. + 6. Path segments consisting of `.` and `..` are resolved as per + https://tools.ietf.org/html/rfc3986#section-6.2.2.3[RFC 3986 6.2.2.3]. + 7. Further, empty path segments are removed, meaning that duplicate + slash (`/`) separators are removed from the path. + 8. If a port was not specified, but the scheme defines a default + port, then `u_port` will be filled in with the value of the default port. + + +== RETURN VALUES + +This function returns 0 on success, and non-zero otherwise. + + +== ERRORS + +`NNG_ENOMEM`:: Insufficient free memory exists to allocate a message. +`NNG_EINVAL`:: An invalid URL was supplied. + + +== SEE ALSO + +<>, +<>, +<>, +<> + + +== COPYRIGHT + +Copyright 2018 mailto:info@staysail.tech[Staysail Systems, Inc.] + +Copyright 2018 mailto:info@capitar.com[Capitar IT Group BV] + +This document is supplied under the terms of the +https://opensource.org/licenses/MIT[MIT License]. diff --git a/src/core/defs.h b/src/core/defs.h index d4a8a740..cecb4825 100644 --- a/src/core/defs.h +++ b/src/core/defs.h @@ -33,6 +33,7 @@ typedef struct nng_msg nni_msg; typedef struct nng_sockaddr nni_sockaddr; typedef struct nng_event nni_event; typedef struct nng_notify nni_notify; +typedef struct nng_url nni_url; // These are our own names. typedef struct nni_socket nni_sock; @@ -43,7 +44,6 @@ typedef struct nni_tran_ep nni_tran_ep; typedef struct nni_tran_ep_option nni_tran_ep_option; typedef struct nni_tran_pipe nni_tran_pipe; typedef struct nni_tran_pipe_option nni_tran_pipe_option; -typedef struct nni_url nni_url; typedef struct nni_proto_sock_ops nni_proto_sock_ops; typedef struct nni_proto_pipe_ops nni_proto_pipe_ops; diff --git a/src/nng.c b/src/nng.c index 509bb333..addfc06a 100644 --- a/src/nng.c +++ b/src/nng.c @@ -1176,3 +1176,21 @@ nng_thread_destroy(void *arg) NNI_FREE_STRUCT(thr); } + +int +nng_url_parse(nng_url **result, const char *ustr) +{ + return (nni_url_parse(result, ustr)); +} + +void +nng_url_free(nng_url *url) +{ + nni_url_free(url); +} + +int +nng_url_clone(nng_url **dstp, const nng_url *src) +{ + return (nni_url_clone(dstp, src)); +} \ No newline at end of file diff --git a/src/nng.h b/src/nng.h index 26e851eb..a25cb25c 100644 --- a/src/nng.h +++ b/src/nng.h @@ -710,6 +710,35 @@ NNG_DECL int nng_tls_config_ca_file(nng_tls_config *, const char *); NNG_DECL int nng_tls_config_cert_key_file( nng_tls_config *, const char *, const char *); +// URL support. We frequently want to process a URL, and these methods +// give us a convenient way of doing so. + +typedef struct nng_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 + char *u_rawpath; // includes query and fragment, "" if not specified +} nng_url; + +// nng_url_parse parses a URL string into a structured form. +// Note that the u_port member will be filled out with a numeric +// port if one isn't specified and a default port is appropriate for +// the scheme. The URL structure is allocated, along with individual +// members. It can be freed with nng_url_free. +NNG_DECL int nng_url_parse(nng_url **, const char *); + +// nng_url_free frees a URL structure that was created by nng_url_parse9(). +NNG_DECL void nng_url_free(nng_url *); + +// nng_url_clone clones a URL structure. +NNG_DECL int nng_url_clone(nng_url **, const nng_url *); + #ifdef __cplusplus } #endif diff --git a/tests/url.c b/tests/url.c index dba0664b..a4b4457b 100644 --- a/tests/url.c +++ b/tests/url.c @@ -12,17 +12,14 @@ #include "convey.h" -#include "core/nng_impl.h" -#include "core/url.h" - -//#include "stubs.h" +#include "nng.h" TestMain("URLs", { - nni_url *url; + nng_url *url; Convey("http://www.google.com", { - So(nni_url_parse(&url, "http://www.google.com") == 0); + So(nng_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); @@ -33,11 +30,11 @@ TestMain("URLs", { So(url->u_query == NULL); So(url->u_fragment == NULL); So(url->u_userinfo == NULL); - nni_url_free(url); + nng_url_free(url); }); Convey("http://www.google.com:1234", { - So(nni_url_parse(&url, "http://www.google.com:1234") == 0); + So(nng_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); @@ -48,11 +45,11 @@ TestMain("URLs", { So(url->u_query == NULL); So(url->u_fragment == NULL); So(url->u_userinfo == NULL); - nni_url_free(url); + nng_url_free(url); }); Convey("http://www.google.com:1234/somewhere", { - So(nni_url_parse( + So(nng_url_parse( &url, "http://www.google.com:1234/somewhere") == 0); So(url != NULL); So(strcmp(url->u_scheme, "http") == 0); @@ -64,10 +61,10 @@ TestMain("URLs", { So(url->u_userinfo == NULL); So(url->u_query == NULL); So(url->u_fragment == NULL); - nni_url_free(url); + nng_url_free(url); }); Convey("http://garrett@www.google.com:1234/somewhere", { - So(nni_url_parse(&url, + So(nng_url_parse(&url, "http://garrett@www.google.com:1234/somewhere") == 0); So(url != NULL); So(strcmp(url->u_scheme, "http") == 0); @@ -79,10 +76,10 @@ TestMain("URLs", { So(strcmp(url->u_rawpath, "/somewhere") == 0); So(url->u_query == NULL); So(url->u_fragment == NULL); - nni_url_free(url); + nng_url_free(url); }); Convey("http://www.google.com/somewhere?result=yes", { - So(nni_url_parse(&url, + So(nng_url_parse(&url, "http://www.google.com/somewhere?result=yes") == 0); So(url != NULL); So(strcmp(url->u_scheme, "http") == 0); @@ -94,10 +91,10 @@ TestMain("URLs", { So(strcmp(url->u_rawpath, "/somewhere?result=yes") == 0); So(url->u_userinfo == NULL); So(url->u_fragment == NULL); - nni_url_free(url); + nng_url_free(url); }); Convey("http://www.google.com/somewhere?result=yes#chapter1", { - So(nni_url_parse(&url, + So(nng_url_parse(&url, "http://www.google.com/" "somewhere?result=yes#chapter1") == 0); So(url != NULL); @@ -111,10 +108,10 @@ TestMain("URLs", { So(strcmp(url->u_rawpath, "/somewhere?result=yes#chapter1") == 0); So(url->u_userinfo == NULL); - nni_url_free(url); + nng_url_free(url); }); Convey("http://www.google.com/somewhere#chapter2", { - So(nni_url_parse( + So(nng_url_parse( &url, "http://www.google.com/somewhere#chapter2") == 0); So(url != NULL); So(strcmp(url->u_scheme, "http") == 0); @@ -126,10 +123,10 @@ TestMain("URLs", { So(strcmp(url->u_rawpath, "/somewhere#chapter2") == 0); So(url->u_query == NULL); So(url->u_userinfo == NULL); - nni_url_free(url); + nng_url_free(url); }); Convey("http://www.google.com#chapter3", { - So(nni_url_parse(&url, "http://www.google.com#chapter3") == 0); + So(nng_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); @@ -140,10 +137,10 @@ TestMain("URLs", { So(strcmp(url->u_rawpath, "#chapter3") == 0); So(url->u_query == NULL); So(url->u_userinfo == NULL); - nni_url_free(url); + nng_url_free(url); }); Convey("http://www.google.com?color=red", { - So(nni_url_parse(&url, "http://www.google.com?color=red") == + So(nng_url_parse(&url, "http://www.google.com?color=red") == 0); So(url != NULL); So(strcmp(url->u_scheme, "http") == 0); @@ -155,11 +152,11 @@ TestMain("URLs", { So(strcmp(url->u_rawpath, "?color=red") == 0); So(url->u_fragment == NULL); So(url->u_userinfo == NULL); - nni_url_free(url); + nng_url_free(url); }); Convey("http://[::1]", { - So(nni_url_parse(&url, "http://[::1]") == 0); + So(nng_url_parse(&url, "http://[::1]") == 0); So(url != NULL); So(strcmp(url->u_scheme, "http") == 0); So(strcmp(url->u_host, "[::1]") == 0); @@ -169,11 +166,11 @@ TestMain("URLs", { So(url->u_query == NULL); So(url->u_fragment == NULL); So(url->u_userinfo == NULL); - nni_url_free(url); + nng_url_free(url); }); Convey("http://[::1]:29", { - So(nni_url_parse(&url, "http://[::1]:29") == 0); + So(nng_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); @@ -183,10 +180,10 @@ TestMain("URLs", { So(url->u_query == NULL); So(url->u_fragment == NULL); So(url->u_userinfo == NULL); - nni_url_free(url); + nng_url_free(url); }); Convey("http://[::1]:29/bottles", { - So(nni_url_parse(&url, "http://[::1]:29/bottles") == 0); + So(nng_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); @@ -196,11 +193,11 @@ TestMain("URLs", { So(url->u_query == NULL); So(url->u_fragment == NULL); So(url->u_userinfo == NULL); - nni_url_free(url); + nng_url_free(url); }); Convey("tcp://:9876/", { - So(nni_url_parse(&url, "tcp://:9876/") == 0); + So(nng_url_parse(&url, "tcp://:9876/") == 0); So(url != NULL); So(strcmp(url->u_scheme, "tcp") == 0); So(strcmp(url->u_host, ":9876") == 0); @@ -210,11 +207,11 @@ TestMain("URLs", { So(url->u_query == NULL); So(url->u_fragment == NULL); So(url->u_userinfo == NULL); - nni_url_free(url); + nng_url_free(url); }); Convey("ws://", { - So(nni_url_parse(&url, "ws://") == 0); + So(nng_url_parse(&url, "ws://") == 0); So(url != NULL); So(strcmp(url->u_scheme, "ws") == 0); So(strcmp(url->u_host, "") == 0); @@ -224,11 +221,11 @@ TestMain("URLs", { So(url->u_query == NULL); So(url->u_fragment == NULL); So(url->u_userinfo == NULL); - nni_url_free(url); + nng_url_free(url); }); Convey("ssh://user@host.example.com", { - So(nni_url_parse(&url, "ssh://user@host.example.com") == 0); + So(nng_url_parse(&url, "ssh://user@host.example.com") == 0); So(url != NULL); So(strcmp(url->u_scheme, "ssh") == 0); So(strcmp(url->u_host, "host.example.com") == 0); @@ -238,60 +235,60 @@ TestMain("URLs", { So(url->u_query == NULL); So(url->u_fragment == NULL); So(strcmp(url->u_userinfo, "user") == 0); - nni_url_free(url); + nng_url_free(url); }); Convey("Negative www.google.com", { url = NULL; - So(nni_url_parse(&url, "www.google.com") == NNG_EINVAL); + So(nng_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(nng_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(nng_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(nng_url_parse(&url, "http://[::1]bogus") == NNG_EINVAL); So(url == NULL); }); Convey("Canonicalization works", { url = NULL; - So(nni_url_parse(&url, + So(nng_url_parse(&url, "hTTp://www.EXAMPLE.com/bogus/.%2e/%7egarrett") == 0); So(url != NULL); So(strcmp(url->u_scheme, "http") == 0); So(strcmp(url->u_hostname, "www.example.com") == 0); So(strcmp(url->u_port, "80") == 0); So(strcmp(url->u_path, "/~garrett") == 0); - nni_url_free(url); + nng_url_free(url); }); Convey("Path resolution works", { url = NULL; - So(nni_url_parse(&url, + So(nng_url_parse(&url, "http://www.x.com//abc/def/./x/..///./../y") == 0); So(url != NULL); So(strcmp(url->u_scheme, "http") == 0); So(strcmp(url->u_hostname, "www.x.com") == 0); So(strcmp(url->u_port, "80") == 0); So(strcmp(url->u_path, "/abc/y") == 0); - nni_url_free(url); + nng_url_free(url); }); Convey("Query info unmolested", { url = NULL; - So(nni_url_parse( + So(nng_url_parse( &url, "http://www.x.com/?/abc/def/./x/.././../y") == 0); So(url != NULL); So(strcmp(url->u_scheme, "http") == 0); @@ -299,25 +296,25 @@ TestMain("URLs", { So(strcmp(url->u_port, "80") == 0); So(strcmp(url->u_path, "/") == 0); So(strcmp(url->u_query, "/abc/def/./x/.././../y") == 0); - nni_url_free(url); + nng_url_free(url); }); Convey("Bad UTF-8 fails", { url = NULL; - So(nni_url_parse(&url, "http://x.com/x%80x") == NNG_EINVAL); - So(nni_url_parse(&url, "http://x.com/x%c0%81") == NNG_EINVAL); + So(nng_url_parse(&url, "http://x.com/x%80x") == NNG_EINVAL); + So(nng_url_parse(&url, "http://x.com/x%c0%81") == NNG_EINVAL); }); Convey("Valid UTF-8 works", { url = NULL; - So(nni_url_parse(&url, "http://www.x.com/%c2%a2_centsign") == + So(nng_url_parse(&url, "http://www.x.com/%c2%a2_centsign") == 0); So(url != NULL); So(strcmp(url->u_scheme, "http") == 0); So(strcmp(url->u_hostname, "www.x.com") == 0); So(strcmp(url->u_port, "80") == 0); So(strcmp(url->u_path, "/\xc2\xa2_centsign") == 0); - nni_url_free(url); + nng_url_free(url); }); }) -- cgit v1.2.3-70-g09d2