[openssl] master update

dev at ddvo.net dev at ddvo.net
Tue Sep 8 13:37:50 UTC 2020


The branch master has been updated
       via  d96486dc809b5d134055785bfa6d707195d95534 (commit)
       via  6e477a60e42978f63623ad64d8e28e7a3e5f2e28 (commit)
       via  d7fcee3b3b5fae674f107c736f8d53610212ce4e (commit)
      from  8d6481f532ab8c502de2ad17e09f688abb675a71 (commit)


- Log -----------------------------------------------------------------
commit d96486dc809b5d134055785bfa6d707195d95534
Author: Dr. David von Oheimb <David.von.Oheimb at siemens.com>
Date:   Fri Sep 4 08:11:41 2020 +0200

    apps/cmp.c: Allow default HTTP path (aka CMP alias) given with -server option
    
    Reviewed-by: Shane Lontis <shane.lontis at oracle.com>
    (Merged from https://github.com/openssl/openssl/pull/12786)

commit 6e477a60e42978f63623ad64d8e28e7a3e5f2e28
Author: Dr. David von Oheimb <David.von.Oheimb at siemens.com>
Date:   Fri Sep 4 08:05:46 2020 +0200

    apps/cmp.c: Use enhanced OSSL_HTTP_parse_url(), removing parse_addr() and atoint()
    
    Reviewed-by: Shane Lontis <shane.lontis at oracle.com>
    (Merged from https://github.com/openssl/openssl/pull/12786)

commit d7fcee3b3b5fae674f107c736f8d53610212ce4e
Author: Dr. David von Oheimb <David.von.Oheimb at siemens.com>
Date:   Thu Sep 3 13:32:56 2020 +0200

    OSSL_HTTP_parse_url(): add optional port number return parameter and strengthen documentation
    
    Reviewed-by: Shane Lontis <shane.lontis at oracle.com>
    (Merged from https://github.com/openssl/openssl/pull/12786)

-----------------------------------------------------------------------

Summary of changes:
 apps/cmp.c                                         |  93 ++++----------
 apps/lib/apps.c                                    |   2 +-
 apps/ocsp.c                                        |   2 +-
 apps/s_server.c                                    |   4 +-
 crypto/err/openssl.txt                             |   3 +
 crypto/http/http_client.c                          |   3 +-
 crypto/http/http_err.c                             |   5 +
 crypto/http/http_lib.c                             | 102 ++++++++++------
 doc/man1/openssl-cmp.pod.in                        |   7 +-
 doc/man3/OSSL_HTTP_transfer.pod                    | 136 +++++++++++----------
 include/openssl/http.h                             |   2 +-
 include/openssl/httperr.h                          |   4 +
 test/http_test.c                                   |  72 +++++++++++
 .../81-test_cmp_cli_data/test_connection.csv       |   3 +
 14 files changed, 260 insertions(+), 178 deletions(-)

diff --git a/apps/cmp.c b/apps/cmp.c
index 9846e7a9c2..dd49142309 100644
--- a/apps/cmp.c
+++ b/apps/cmp.c
@@ -74,11 +74,10 @@ typedef enum {
 
 /* message transfer */
 static char *opt_server = NULL;
-static char server_port_s[32] = { '\0' };
-static int server_port = 0;
+static char server_port[32] = { '\0' };
 static char *opt_proxy = NULL;
 static char *opt_no_proxy = NULL;
-static char *opt_path = "/";
+static char *opt_path = NULL;
 static int opt_msg_timeout = -1;
 static int opt_total_timeout = -1;
 
@@ -334,9 +333,9 @@ const OPTIONS cmp_options[] = {
 
     OPT_SECTION("Message transfer"),
     {"server", OPT_SERVER, 's',
-     "[http[s]://]address[:port] of CMP server. Default port 80 or 443."},
+     "[http[s]://]address[:port][/path] of CMP server. Default port 80 or 443."},
     {OPT_MORE_STR, 0, 0,
-     "The address may be a DNS name or an IP address"},
+     "address may be a DNS name or an IP address; path can be overridden by -path"},
     {"proxy", OPT_PROXY, 's',
      "[http[s]://]address[:port][/path] of HTTP(S) proxy to use; path is ignored"},
     {"no_proxy", OPT_NO_PROXY, 's',
@@ -344,7 +343,7 @@ const OPTIONS cmp_options[] = {
     {OPT_MORE_STR, 0, 0,
      "Default from environment variable 'no_proxy', else 'NO_PROXY', else none"},
     {"path", OPT_PATH, 's',
-     "HTTP path (aka CMP alias) at the CMP server. Default \"/\""},
+     "HTTP path (aka CMP alias) at the CMP server. Default from -server, else \"/\""},
     {"msg_timeout", OPT_MSG_TIMEOUT, 'n',
      "Timeout per CMP message round trip (or 0 for none). Default 120 seconds"},
     {"total_timeout", OPT_TOTAL_TIMEOUT, 'n',
@@ -889,49 +888,6 @@ static OSSL_CMP_MSG *read_write_req_resp(OSSL_CMP_CTX *ctx,
     return res;
 }
 
-/*
- * parse string as integer value, not allowing trailing garbage, see also
- * https://www.gnu.org/software/libc/manual/html_node/Parsing-of-Integers.html
- *
- * returns integer value, or INT_MIN on error
- */
-static int atoint(const char *str)
-{
-    char *tailptr;
-    long res = strtol(str, &tailptr, 10);
-
-    if  ((*tailptr != '\0') || (res < INT_MIN) || (res > INT_MAX))
-        return INT_MIN;
-    else
-        return (int)res;
-}
-
-static int parse_addr(char **opt_string, int port, const char *name)
-{
-    char *port_string;
-
-    if (strncasecmp(*opt_string, OSSL_HTTP_PREFIX,
-                    strlen(OSSL_HTTP_PREFIX)) == 0) {
-        *opt_string += strlen(OSSL_HTTP_PREFIX);
-    } else if (strncasecmp(*opt_string, OSSL_HTTPS_PREFIX,
-                           strlen(OSSL_HTTPS_PREFIX)) == 0) {
-        *opt_string += strlen(OSSL_HTTPS_PREFIX);
-        if (port == 0)
-            port = 443; /* == integer value of OSSL_HTTPS_PORT */
-    }
-
-    if ((port_string = strrchr(*opt_string, ':')) == NULL)
-        return port; /* using default */
-    *(port_string++) = '\0';
-    port = atoint(port_string);
-    if ((port <= 0) || (port > 65535)) {
-        CMP_err2("invalid %s port '%s' given, sane range 1-65535",
-                 name, port_string);
-        return -1;
-    }
-    return port;
-}
-
 static int set1_store_parameters(X509_STORE *ts)
 {
     if (ts == NULL)
@@ -1896,33 +1852,36 @@ static int handle_opt_geninfo(OSSL_CMP_CTX *ctx)
 static int setup_client_ctx(OSSL_CMP_CTX *ctx, ENGINE *engine)
 {
     int ret = 0;
+    char *server = NULL, *port = NULL, *path = NULL, *used_path;
+    int portnum, ssl;
     char server_buf[200] = { '\0' };
     char proxy_buf[200] = { '\0' };
     char *proxy_host = NULL;
     char *proxy_port_str = NULL;
 
     if (opt_server == NULL) {
-        CMP_err("missing server address[:port]");
+        CMP_err("missing -server option");
+        goto err;
+    }
+    if (!OSSL_HTTP_parse_url(opt_server, &server, &port, &portnum, &path, &ssl))
         goto err;
-    } else if ((server_port =
-                parse_addr(&opt_server, server_port, "server")) < 0) {
+    if (ssl && !opt_tls_used) {
+        CMP_err("missing -tls_used option since -server URL indicates https");
         goto err;
     }
-    if (server_port != 0)
-        BIO_snprintf(server_port_s, sizeof(server_port_s), "%d", server_port);
-    if (!OSSL_CMP_CTX_set1_server(ctx, opt_server)
-            || !OSSL_CMP_CTX_set_serverPort(ctx, server_port)
-            || !OSSL_CMP_CTX_set1_serverPath(ctx, opt_path))
+    strncpy(server_port, port, sizeof(server_port));
+    used_path = opt_path != NULL ? opt_path : path;
+    if (!OSSL_CMP_CTX_set1_server(ctx, server)
+            || !OSSL_CMP_CTX_set_serverPort(ctx, portnum)
+            || !OSSL_CMP_CTX_set1_serverPath(ctx, used_path))
         goto oom;
     if (opt_proxy != NULL && !OSSL_CMP_CTX_set1_proxy(ctx, opt_proxy))
         goto oom;
     if (opt_no_proxy != NULL && !OSSL_CMP_CTX_set1_no_proxy(ctx, opt_no_proxy))
         goto oom;
-    (void)BIO_snprintf(server_buf, sizeof(server_buf), "http%s://%s%s%s/%s",
-                       opt_tls_used ? "s" : "", opt_server,
-                       server_port == 0 ? "" : ":", server_port_s,
-                       opt_path == NULL ? "" :
-                       opt_path[0] == '/' ? opt_path + 1 : opt_path);
+    (void)BIO_snprintf(server_buf, sizeof(server_buf), "http%s://%s:%s/%s",
+                       opt_tls_used ? "s" : "", server, port,
+                       *used_path == '/' ? used_path + 1 : used_path);
 
     if (opt_proxy != NULL)
         (void)BIO_snprintf(proxy_buf, sizeof(proxy_buf), " via %s", opt_proxy);
@@ -2023,7 +1982,7 @@ static int setup_client_ctx(OSSL_CMP_CTX *ctx, ENGINE *engine)
         (void)OSSL_CMP_CTX_set_http_cb_arg(ctx, info);
         /* info will be freed along with CMP ctx */
         info->server = opt_server;
-        info->port = server_port_s;
+        info->port = server_port;
         info->use_proxy = opt_proxy != NULL;
         info->timeout = OSSL_CMP_CTX_get_option(ctx, OSSL_CMP_OPT_MSG_TIMEOUT);
         info->ssl_ctx = setup_ssl_ctx(ctx, engine);
@@ -2053,6 +2012,9 @@ static int setup_client_ctx(OSSL_CMP_CTX *ctx, ENGINE *engine)
     ret = 1;
 
  err:
+    OPENSSL_free(server);
+    OPENSSL_free(port);
+    OPENSSL_free(path);
     OPENSSL_free(proxy_host);
     OPENSSL_free(proxy_port_str);
     return ret;
@@ -2875,11 +2837,6 @@ int cmp_main(int argc, char **argv)
         }
         opt_server = mock_server;
         opt_proxy = "API";
-    } else {
-        if (opt_server == NULL) {
-            CMP_err("missing -server option");
-            goto err;
-        }
     }
 
     if (!setup_client_ctx(cmp_ctx, engine)) {
diff --git a/apps/lib/apps.c b/apps/lib/apps.c
index 150df997b8..342c364aa4 100644
--- a/apps/lib/apps.c
+++ b/apps/lib/apps.c
@@ -2066,7 +2066,7 @@ ASN1_VALUE *app_http_get_asn1(const char *url, const char *proxy,
         return NULL;
     }
 
-    if (!OSSL_HTTP_parse_url(url, &server, &port, NULL /* ppath */, &use_ssl))
+    if (!OSSL_HTTP_parse_url(url, &server, &port, NULL, NULL, &use_ssl))
         return NULL;
     if (use_ssl && ssl_ctx == NULL) {
         HTTPerr(0, ERR_R_PASSED_NULL_PARAMETER);
diff --git a/apps/ocsp.c b/apps/ocsp.c
index 8fb605e6fe..0aca4b7622 100644
--- a/apps/ocsp.c
+++ b/apps/ocsp.c
@@ -275,7 +275,7 @@ int ocsp_main(int argc, char **argv)
             OPENSSL_free(tpath);
             thost = tport = tpath = NULL;
             if (!OSSL_HTTP_parse_url(opt_arg(),
-                                     &host, &port, &path, &use_ssl)) {
+                                     &host, &port, NULL, &path, &use_ssl)) {
                 BIO_printf(bio_err, "%s Error parsing URL\n", prog);
                 goto end;
             }
diff --git a/apps/s_server.c b/apps/s_server.c
index b936ff4226..4c2e5b27f7 100644
--- a/apps/s_server.c
+++ b/apps/s_server.c
@@ -535,7 +535,7 @@ static int get_ocsp_resp_from_responder(SSL *s, tlsextstatusctx *srctx,
     aia = X509_get1_ocsp(x);
     if (aia != NULL) {
         if (!OSSL_HTTP_parse_url(sk_OPENSSL_STRING_value(aia, 0),
-                                 &host, &port, &path, &use_ssl)) {
+                                 &host, &port, NULL, &path, &use_ssl)) {
             BIO_puts(bio_err, "cert_status: can't parse AIA URL\n");
             goto err;
         }
@@ -1405,7 +1405,7 @@ int s_server_main(int argc, char *argv[])
 #ifndef OPENSSL_NO_OCSP
             s_tlsextstatus = 1;
             if (!OSSL_HTTP_parse_url(opt_arg(),
-                                     &tlscstatp.host, &tlscstatp.port,
+                                     &tlscstatp.host, &tlscstatp.port, NULL,
                                      &tlscstatp.path, &tlscstatp.use_ssl)) {
                 BIO_printf(bio_err, "Error parsing URL\n");
                 goto end;
diff --git a/crypto/err/openssl.txt b/crypto/err/openssl.txt
index 7c37a3e56e..256ec35588 100644
--- a/crypto/err/openssl.txt
+++ b/crypto/err/openssl.txt
@@ -2637,6 +2637,9 @@ HTTP_R_ERROR_PARSING_URL:101:error parsing url
 HTTP_R_ERROR_RECEIVING:103:error receiving
 HTTP_R_ERROR_SENDING:102:error sending
 HTTP_R_INCONSISTENT_CONTENT_LENGTH:120:inconsistent content length
+HTTP_R_INVALID_PORT_NUMBER:123:invalid port number
+HTTP_R_INVALID_URL_PATH:125:invalid url path
+HTTP_R_INVALID_URL_PREFIX:124:invalid url prefix
 HTTP_R_MAX_RESP_LEN_EXCEEDED:117:max resp len exceeded
 HTTP_R_MISSING_ASN1_ENCODING:110:missing asn1 encoding
 HTTP_R_MISSING_CONTENT_TYPE:121:missing content type
diff --git a/crypto/http/http_client.c b/crypto/http/http_client.c
index 3e1be1f569..5a78d67ca4 100644
--- a/crypto/http/http_client.c
+++ b/crypto/http/http_client.c
@@ -1005,7 +1005,8 @@ BIO *OSSL_HTTP_get(const char *url, const char *proxy, const char *no_proxy,
         return NULL;
 
     for (;;) {
-        if (!OSSL_HTTP_parse_url(current_url, &host, &port, &path, &use_ssl))
+        if (!OSSL_HTTP_parse_url(current_url, &host, &port, NULL /* port_num */,
+                                 &path, &use_ssl))
             break;
 
      new_rpath:
diff --git a/crypto/http/http_err.c b/crypto/http/http_err.c
index 7b6f295170..13779fac84 100644
--- a/crypto/http/http_err.c
+++ b/crypto/http/http_err.c
@@ -26,6 +26,11 @@ static const ERR_STRING_DATA HTTP_str_reasons[] = {
     {ERR_PACK(ERR_LIB_HTTP, 0, HTTP_R_ERROR_SENDING), "error sending"},
     {ERR_PACK(ERR_LIB_HTTP, 0, HTTP_R_INCONSISTENT_CONTENT_LENGTH),
     "inconsistent content length"},
+    {ERR_PACK(ERR_LIB_HTTP, 0, HTTP_R_INVALID_PORT_NUMBER),
+    "invalid port number"},
+    {ERR_PACK(ERR_LIB_HTTP, 0, HTTP_R_INVALID_URL_PATH), "invalid url path"},
+    {ERR_PACK(ERR_LIB_HTTP, 0, HTTP_R_INVALID_URL_PREFIX),
+    "invalid url prefix"},
     {ERR_PACK(ERR_LIB_HTTP, 0, HTTP_R_MAX_RESP_LEN_EXCEEDED),
     "max resp len exceeded"},
     {ERR_PACK(ERR_LIB_HTTP, 0, HTTP_R_MISSING_ASN1_ENCODING),
diff --git a/crypto/http/http_lib.c b/crypto/http/http_lib.c
index 5da5b1e724..19b964e613 100644
--- a/crypto/http/http_lib.c
+++ b/crypto/http/http_lib.c
@@ -21,19 +21,12 @@
  */
 
 int OSSL_HTTP_parse_url(const char *url, char **phost, char **pport,
-                        char **ppath, int *pssl)
+                        int *pport_num, char **ppath, int *pssl)
 {
     char *p, *buf;
-    char *host;
-    const char *port = OSSL_HTTP_PORT;
-    size_t https_len = strlen(OSSL_HTTPS_NAME);
-
-    if (!ossl_assert(https_len >= strlen(OSSL_HTTP_NAME)))
-        return 0;
-    if (url == NULL) {
-        HTTPerr(0, ERR_R_PASSED_NULL_PARAMETER);
-        return 0;
-    }
+    char *host, *host_end;
+    const char *path, *port = OSSL_HTTP_PORT;
+    long portnum = 80;
 
     if (phost != NULL)
         *phost = NULL;
@@ -44,59 +37,90 @@ int OSSL_HTTP_parse_url(const char *url, char **phost, char **pport,
     if (pssl != NULL)
         *pssl = 0;
 
+    if (url == NULL) {
+        HTTPerr(0, ERR_R_PASSED_NULL_PARAMETER);
+        return 0;
+    }
+
     /* dup the buffer since we are going to mess with it */
     if ((buf = OPENSSL_strdup(url)) == NULL)
         goto err;
 
-    /* Check for initial colon */
-    p = strchr(buf, ':');
-    if (p == NULL || (size_t)(p - buf) > https_len) {
+    /* check for optional prefix "http[s]://" */
+    p = strstr(buf, "://");
+    if (p == NULL) {
         p = buf;
     } else {
-        *(p++) = '\0';
-
+        *p = '\0'; /* p points to end of scheme name */
         if (strcmp(buf, OSSL_HTTPS_NAME) == 0) {
             if (pssl != NULL)
                 *pssl = 1;
             port = OSSL_HTTPS_PORT;
+            portnum = 443;
         } else if (strcmp(buf, OSSL_HTTP_NAME) != 0) {
-            goto parse_err;
+            HTTPerr(0, HTTP_R_INVALID_URL_PREFIX);
+            goto err;
         }
-
-        /* Check for double slash */
-        if ((p[0] != '/') || (p[1] != '/'))
-            goto parse_err;
-        p += 2;
+        p += 3;
     }
     host = p;
 
-    /* Check for trailing part of path */
-    p = strchr(p, '/');
-    if (ppath != NULL && (*ppath = OPENSSL_strdup(p == NULL ? "/" : p)) == NULL)
-        goto err;
-    if (p != NULL)
-        *p = '\0'; /* Set start of path to 0 so hostname[:port] is valid */
-
-    p = host;
+    /* parse host name/address as far as needed here */
     if (host[0] == '[') {
-        /* ipv6 literal */
+        /* ipv6 literal, which may include ':' */
         host++;
-        p = strchr(host, ']');
-        if (p == NULL)
+        host_end = strchr(host, ']');
+        if (host_end == NULL)
             goto parse_err;
-        *p = '\0';
-        p++;
+        *host_end++ = '\0';
+    } else {
+        host_end = strchr(host, ':'); /* look for start of optional port */
+        if (host_end == NULL)
+            host_end = strchr(host, '/'); /* look for start of optional path */
+        if (host_end == NULL)
+            /* the remaining string is just the hostname */
+            host_end = host + strlen(host);
     }
 
-    /* Look for optional ':' for port number */
-    if ((p = strchr(p, ':'))) {
-        *p = '\0';
-        port = p + 1;
+    /* parse optional port specification starting with ':' */
+    p = host_end;
+    if (*p == ':') {
+        port = ++p;
+        if (pport_num == NULL) {
+            p = strchr(port, '/');
+            if (p == NULL)
+                p = p + strlen(port);
+        } else { /* make sure a numerical port value is given */
+            portnum = strtol(port, &p, 10);
+            if (p == port || (*p != '\0' && *p != '/'))
+                goto parse_err;
+            if (portnum <= 0 || portnum >= 65536) {
+                HTTPerr(0, HTTP_R_INVALID_PORT_NUMBER);
+                goto err;
+            }
+        }
+    }
+    *host_end = '\0';
+    *p = '\0'; /* terminate port string */
+
+    /* check for optional path at end of url starting with '/' */
+    path = url + (p - buf);
+    /* cannot use p + 1 because *p is '\0' and path must start with '/' */
+    if (*path == '\0') {
+        path = "/";
+    } else if (*path != '/') {
+        HTTPerr(0, HTTP_R_INVALID_URL_PATH);
+        goto parse_err;
     }
+
     if (phost != NULL && (*phost = OPENSSL_strdup(host)) == NULL)
         goto err;
     if (pport != NULL && (*pport = OPENSSL_strdup(port)) == NULL)
         goto err;
+    if (pport_num != NULL)
+        *pport_num = (int)portnum;
+    if (ppath != NULL && (*ppath = OPENSSL_strdup(path)) == NULL)
+        goto err;
 
     OPENSSL_free(buf);
     return 1;
diff --git a/doc/man1/openssl-cmp.pod.in b/doc/man1/openssl-cmp.pod.in
index 3dc193cd4d..46c5059d84 100644
--- a/doc/man1/openssl-cmp.pod.in
+++ b/doc/man1/openssl-cmp.pod.in
@@ -12,7 +12,7 @@ B<openssl> B<cmp>
 [B<-config> I<filename>]
 [B<-section> I<names>]
 
-[B<-server> I<address[:port]>]
+[B<-server> I<[http[s]://]address[:port][/path]>]
 [B<-proxy> I<[http[s]://]address[:port][/path]>]
 [B<-no_proxy> I<addresses>]
 [B<-path> I<remote_path>]
@@ -431,11 +431,12 @@ Reason numbers defined in RFC 5280 are:
 
 =over 4
 
-=item B<-server> I<[http[s]://]address[:port]>
+=item B<-server> I<[http[s]://]address[:port][/path]>
 
 The IP address or DNS hostname and optionally port (defaulting to 80 or 443)
 of the CMP server to connect to using HTTP(S) transport.
 The optional I<http://> or I<https://> prefix is ignored.
+If a path is included it provides the default value for the B<-path> option.
 
 =item B<-proxy> I<[http[s]://]address[:port][/path]>
 
@@ -454,7 +455,7 @@ Default is from the environment variable C<no_proxy> if set, else C<NO_PROXY>.
 =item B<-path> I<remote_path>
 
 HTTP path at the CMP server (aka CMP alias) to use for POST requests.
-Defaults to I</>.
+Defaults to any path given with B<-server>, else C<"/">.
 
 =item B<-msg_timeout> I<seconds>
 
diff --git a/doc/man3/OSSL_HTTP_transfer.pod b/doc/man3/OSSL_HTTP_transfer.pod
index 34794c313c..f78d96be1f 100644
--- a/doc/man3/OSSL_HTTP_transfer.pod
+++ b/doc/man3/OSSL_HTTP_transfer.pod
@@ -55,36 +55,36 @@ OSSL_HTTP_parse_url
                              const char *proxyuser, const char *proxypass,
                              int timeout, BIO *bio_err, const char *prog);
  int OSSL_HTTP_parse_url(const char *url, char **phost, char **pport,
-                         char **ppath, int *pssl);
+                         int *pport_num, char **ppath, int *pssl);
 
 =head1 DESCRIPTION
 
-OSSL_HTTP_get() uses HTTP GET to obtain data (of any type) from the given B<url>
+OSSL_HTTP_get() uses HTTP GET to obtain data (of any type) from the given I<url>
 and returns it as a memory BIO.
 
 OSSL_HTTP_get_asn1() uses HTTP GET to obtain an ASN.1-encoded value
-(e.g., an X.509 certificate) with the expected structure specified by B<it>
-(e.g., I<ASN1_ITEM_rptr(X509)>) from the given B<url>
+(e.g., an X.509 certificate) with the expected structure specified by I<it>
+(e.g., I<ASN1_ITEM_rptr(X509)>) from the given I<url>
 and returns it on success as a pointer to I<ASN1_VALUE>.
 
-OSSL_HTTP_post_asn1() uses the HTTP POST method to send a request B<req>
-with the ASN.1 structure defined in B<req_it> and the given B<content_type> to
-the given B<server> and optional B<port> and B<path>.
-If B<use_ssl> is nonzero a TLS connection is requested and the B<bio_update_fn>
+OSSL_HTTP_post_asn1() uses the HTTP POST method to send a request I<req>
+with the ASN.1 structure defined in I<req_it> and the given I<content_type> to
+the given I<server> and optional I<port> and I<path>.
+If I<use_ssl> is nonzero a TLS connection is requested and the I<bio_update_fn>
 parameter, described below, must be provided.
-The optional list B<headers> may contain additional custom HTTP header lines.
-The expected structure of the response is specified by B<rsp_it>.
+The optional list I<headers> may contain additional custom HTTP header lines.
+The expected structure of the response is specified by I<rsp_it>.
 On success it returns the response as a pointer to B<ASN1_VALUE>.
 
 OSSL_HTTP_transfer() exchanges any form of HTTP request and response.
 It implements the core of the functions described above.
-If B<path> parameter is NULL it defaults to "/".
-If B<use_ssl> is nonzero a TLS connection is requested
-and the B<bio_update_fn> parameter, described below, must be provided.
-If B<req_mem> is NULL it uses the HTTP GET method, else it uses HTTP POST to
-send a request with the contents of the memory BIO and optional B<content_type>.
-The optional list B<headers> may contain additional custom HTTP header lines.
-If B<req_mem> is NULL (i.e., the HTTP method is GET) and B<redirection_url>
+If I<path> parameter is NULL it defaults to "/".
+If I<use_ssl> is nonzero a TLS connection is requested
+and the I<bio_update_fn> parameter, described below, must be provided.
+If I<req_mem> is NULL it uses the HTTP GET method, else it uses HTTP POST to
+send a request with the contents of the memory BIO and optional I<content_type>.
+The optional list I<headers> may contain additional custom HTTP header lines.
+If I<req_mem> is NULL (i.e., the HTTP method is GET) and I<redirection_url>
 is not NULL the latter pointer is used to provide any new location that
 the server may return with HTTP code 301 (MOVED_PERMANENTLY) or 302 (FOUND).
 In this case the caller is responsible for deallocating this URL with
@@ -93,71 +93,71 @@ L<OPENSSL_free(3)>.
 The above functions have the following parameters in common.
 
 Typically the OpenSSL build supports sockets
-and the B<bio> and B<rbio> parameters are both NULL.
+and the I<bio> and I<rbio> parameters are both NULL.
 In this case the client creates a network BIO internally
-for connecting to the given B<server>
-at the specified B<port> (if any, defaulting to 80 for HTTP or 443 for HTTPS),
-optionally via a B<proxy> (respecting B<no_proxy>) as described below.
+for connecting to the given I<server>
+at the specified I<port> (if any, defaulting to 80 for HTTP or 443 for HTTPS),
+optionally via a I<proxy> (respecting I<no_proxy>) as described below.
 Then the client uses this internal BIO for exchanging the request and response.
-If B<bio> is given and B<rbio> is NULL then the client uses this B<bio> instead.
-If both B<bio> and B<rbio> are given (which may be memory BIOs for instance)
+If I<bio> is given and I<rbio> is NULL then the client uses this I<bio> instead.
+If both I<bio> and I<rbio> are given (which may be memory BIOs for instance)
 then no explicit connection is attempted,
-B<bio> is used for writing the request, and B<rbio> for reading the response.
-As soon as the client has flushed B<bio> the server must be ready to provide
-a response or indicate a waiting condition via B<rbio>.
+I<bio> is used for writing the request, and I<rbio> for reading the response.
+As soon as the client has flushed I<bio> the server must be ready to provide
+a response or indicate a waiting condition via I<rbio>.
 
-The optional B<proxy> parameter can be used to set the address of the an
+The optional I<proxy> parameter can be used to set the address of the an
 HTTP(S) proxy to use (unless overridden by "no_proxy" settings).
-If TLS is not used this defaults to the environment variable B<http_proxy>
-if set, else B<HTTP_PROXY>.
-If B<use_ssl> != 0 it defaults to B<https_proxy> if set, else B<HTTPS_PROXY>.
+If TLS is not used this defaults to the environment variable C<http_proxy>
+if set, else C<HTTP_PROXY>.
+If I<use_ssl> != 0 it defaults to C<https_proxy> if set, else C<HTTPS_PROXY>.
 An empty proxy string specifies not to use a proxy.
-Else the format is I<[http[s]://]address[:port][/path]>,
+Else the format is C<[http[s]://]address[:port][/path]>,
 where any path given is ignored.
 The default proxy port number is 80, or 443 in case "https:" is given.
-The HTTP client functions connect via the given proxy unless the B<server>
-is found in the optional list B<no_proxy> of proxy hostnames (if not NULL;
-default is the environment variable B<no_proxy> if set, else B<NO_PROXY>).
+The HTTP client functions connect via the given proxy unless the I<server>
+is found in the optional list I<no_proxy> of proxy hostnames (if not NULL;
+default is the environment variable C<no_proxy> if set, else C<NO_PROXY>).
 Proxying plain HTTP is supported directly,
 while using a proxy for HTTPS connections requires a suitable callback function
-such as B<OSSL_HTTP_proxy_connect()>, described below.
+such as OSSL_HTTP_proxy_connect(), described below.
 
-The B<maxline> parameter specifies the response header maximum line length,
+The I<maxline> parameter specifies the response header maximum line length,
 where 0 indicates the default value, which currently is 4k.
-The B<max_resp_len> parameter specifies the maximum response length,
+The I<max_resp_len> parameter specifies the maximum response length,
 where 0 indicates the default value, which currently is 100k.
 
 An ASN.1-encoded response is expected by OSSL_HTTP_get_asn1() and
 OSSL_HTTP_post_asn1(), while for OSSL_HTTP_get() or OSSL_HTTP_transfer()
-this is only the case if the B<expect_asn1> parameter is nonzero.
+this is only the case if the I<expect_asn1> parameter is nonzero.
 If the response header contains one or more "Content-Length" header lines and/or
 an ASN.1-encoded response is expected, which should include a total length,
 the length indications received are checked for consistency
 and for not exceeding the maximum response length.
 
-If the parameter B<expected_content_type> (or B<expected_ct>, respectively)
+If the parameter I<expected_content_type> (or I<expected_ct>, respectively)
 is not NULL then the HTTP client checks that the given content type string
 is included in the HTTP header of the response and returns an error if not.
 
-If the B<timeout> parameter is > 0 this indicates the maximum number of seconds
+If the I<timeout> parameter is > 0 this indicates the maximum number of seconds
 to wait until the transfer is complete.
 A value of 0 enables waiting indefinitely,
 while a value < 0 immediately leads to a timeout condition.
 
-The optional parameter B<bio_update_fn> with its optional argument B<arg> may
+The optional parameter I<bio_update_fn> with its optional argument I<arg> may
 be used to modify the connection BIO used by the HTTP client (and cannot be
-used when both B<bio> and B<rbio> are given).
-B<bio_update_fn> is a BIO connect/disconnect callback function with prototype
+used when both I<bio> and I<rbio> are given).
+I<bio_update_fn> is a BIO connect/disconnect callback function with prototype
 
  BIO *(*OSSL_HTTP_bio_cb_t)(BIO *bio, void *arg, int connect, int detail)
 
-The callback may modify the HTTP BIO provided in the B<bio> argument,
-whereby it may make use of a custom defined argument B<arg>,
+The callback may modify the HTTP BIO provided in the I<bio> argument,
+whereby it may make use of a custom defined argument I<arg>,
 which may for instance refer to an I<SSL_CTX> structure.
 During connection establishment, just after calling BIO_do_connect_retry(),
-the function is invoked with the B<connect> argument being 1 and the B<detail>
+the function is invoked with the I<connect> argument being 1 and the I<detail>
 argument being 1 if HTTPS is requested, i.e., SSL/TLS should be enabled.
-On disconnect B<connect> is 0 and B<detail> is 1 if no error occurred, else 0.
+On disconnect I<connect> is 0 and I<detail> is 1 if no error occurred, else 0.
 For instance, on connect the function may prepend a TLS BIO to implement HTTPS;
 after disconnect it may do some diagnostic output and/or specific cleanup.
 The function should return NULL to indicate failure.
@@ -180,31 +180,39 @@ After disconnect the modified BIO will be deallocated using BIO_free_all().
 
 OSSL_HTTP_proxy_connect() may be used by an above BIO connect callback function
 to set up an SSL/TLS connection via an HTTPS proxy.
-It promotes the given BIO B<bio> representing a connection
+It promotes the given BIO I<bio> representing a connection
 pre-established with a TLS proxy using the HTTP CONNECT method,
-optionally using proxy client credentials B<proxyuser> and B<proxypass>,
-to connect with TLS protection ultimately to B<server> and B<port>.
-If the B<port> argument is NULL or the empty string it defaults to "443".
-The B<timeout> parameter is used as described above.
+optionally using proxy client credentials I<proxyuser> and I<proxypass>,
+to connect with TLS protection ultimately to I<server> and I<port>.
+If the I<port> argument is NULL or the empty string it defaults to "443".
+The I<timeout> parameter is used as described above.
 Since this function is typically called by applications such as
-L<openssl-s_client(1)> it uses the B<bio_err> and B<prog> parameters (unless
+L<openssl-s_client(1)> it uses the I<bio_err> and I<prog> parameters (unless
 NULL) to print additional diagnostic information in a user-oriented way.
 
-OSSL_HTTP_parse_url() parses its input string B<url> as a URL and splits it up
-into host, port and path components and a flag whether it begins with 'https'.
-The host component may be a DNS name or an IPv4 or an IPv6 address.
+OSSL_HTTP_parse_url() parses its input string I<url> as a URL
+of the form C<[http[s]://]address[:port][/path]> and splits it up into host,
+port, and path components and a flag indicating whether it begins with 'https'.
+The host component may be a DNS name or an IP address
+where IPv6 addresses should be enclosed in square brackets C<[> and C<]>.
 The port component is optional and defaults to "443" for HTTPS, else "80".
+If the I<pport_num> argument is NULL the port specification
+can be in mnemonic form such as "http" like with L<BIO_set_conn_port(3)>, else
+it must be in numerical form and its integer value is assigned to B<*pport_num>.
 The path component is also optional and defaults to "/".
-As far as the result pointer arguments are not NULL it assigns via
-them copies of the respective string components.
-The strings returned this way must be deallocated by the caller using
-L<OPENSSL_free(3)> unless they are NULL, which is their default value on error.
+On success the function assigns via each non-NULL result pointer argument
+I<phost>, I<pport>, I<pport_num>, I<ppath>, and I<pssl>
+the respective url component.
+On error, B<*phost>, B<*pport>, and B<*ppath> are assigned to NULL,
+else they are guaranteed to contain non-NULL string pointers.
+It is the reponsibility of the caller to free them using L<OPENSSL_free(3)>.
+A string returned via B<*ppath> is guaranteed to begin with a C</> character.
 
 =head1 NOTES
 
 The names of the environment variables used by this implementation:
-B<http_proxy>, B<HTTP_PROXY>, B<https_proxy>, B<HTTPS_PROXY>, B<no_proxy>, and
-B<NO_PROXY>, have been chosen for maximal compatibility with
+C<http_proxy>, C<HTTP_PROXY>, C<https_proxy>, C<HTTPS_PROXY>, C<no_proxy>, and
+C<NO_PROXY>, have been chosen for maximal compatibility with
 other HTTP client implementations such as wget, curl, and git.
 
 =head1 RETURN VALUES
@@ -216,6 +224,10 @@ Error conditions include connection/transfer timeout, parse errors, etc.
 OSSL_HTTP_proxy_connect() and OSSL_HTTP_parse_url()
 return 1 on success, 0 on error.
 
+=head1 SEE ALSO
+
+L<BIO_set_conn_port(3)>
+
 =head1 HISTORY
 
 OSSL_HTTP_get(), OSSL_HTTP_get_asn1(), OSSL_HTTP_post_asn1(),
diff --git a/include/openssl/http.h b/include/openssl/http.h
index 45c8f11d7b..2c9ce9d86e 100644
--- a/include/openssl/http.h
+++ b/include/openssl/http.h
@@ -74,7 +74,7 @@ int OSSL_HTTP_proxy_connect(BIO *bio, const char *server, const char *port,
                             int timeout, BIO *bio_err, const char *prog);
 
 int OSSL_HTTP_parse_url(const char *url, char **phost, char **pport,
-                        char **ppath, int *pssl);
+                        int *pport_num, char **ppath, int *pssl);
 
 # ifdef  __cplusplus
 }
diff --git a/include/openssl/httperr.h b/include/openssl/httperr.h
index e4acb1df8c..7747643bfa 100644
--- a/include/openssl/httperr.h
+++ b/include/openssl/httperr.h
@@ -10,6 +10,7 @@
 
 #ifndef OPENSSL_HTTPERR_H
 # define OPENSSL_HTTPERR_H
+# pragma once
 
 # include <openssl/opensslconf.h>
 # include <openssl/symhacks.h>
@@ -37,6 +38,9 @@ int ERR_load_HTTP_strings(void);
 # define HTTP_R_ERROR_RECEIVING                           103
 # define HTTP_R_ERROR_SENDING                             102
 # define HTTP_R_INCONSISTENT_CONTENT_LENGTH               120
+# define HTTP_R_INVALID_PORT_NUMBER                       123
+# define HTTP_R_INVALID_URL_PATH                          125
+# define HTTP_R_INVALID_URL_PREFIX                        124
 # define HTTP_R_MAX_RESP_LEN_EXCEEDED                     117
 # define HTTP_R_MISSING_ASN1_ENCODING                     110
 # define HTTP_R_MISSING_CONTENT_TYPE                      121
diff --git a/test/http_test.c b/test/http_test.c
index f0b12a7dd0..f073dcd7ff 100644
--- a/test/http_test.c
+++ b/test/http_test.c
@@ -151,6 +151,72 @@ static int test_http_x509(int do_get)
     return res;
 }
 
+static int test_http_url_ok(const char *url, const char *exp_host, int exp_ssl)
+{
+    char *host, *port, *path;
+    int num, ssl;
+    int res;
+
+    res = TEST_true(OSSL_HTTP_parse_url(url, &host, &port, &num, &path, &ssl))
+        && TEST_str_eq(host, exp_host)
+        && TEST_str_eq(port, "65535")
+        && TEST_int_eq(num, 65535)
+        && TEST_str_eq(path, "/pkix")
+        && TEST_int_eq(ssl, exp_ssl);
+    OPENSSL_free(host);
+    OPENSSL_free(port);
+    OPENSSL_free(path);
+    return res;
+}
+
+static int test_http_url_dns(void)
+{
+    return test_http_url_ok("server:65535/pkix", "server", 0);
+}
+
+static int test_http_url_ipv4(void)
+{
+    return test_http_url_ok("https://1.2.3.4:65535/pkix", "1.2.3.4", 1);
+}
+
+static int test_http_url_ipv6(void)
+{
+    return test_http_url_ok("http://[FF01::101]:65535/pkix", "FF01::101", 0);
+}
+
+static int test_http_url_invalid(const char *url)
+{
+    char *host = "1", *port = "1", *path = "1";
+    int num = 1, ssl = 1;
+    int res;
+
+    res = TEST_false(OSSL_HTTP_parse_url(url, &host, &port, &num, &path, &ssl))
+        && TEST_ptr_null(host)
+        && TEST_ptr_null(port)
+        && TEST_ptr_null(path);
+    if (!res) {
+        OPENSSL_free(host);
+        OPENSSL_free(port);
+        OPENSSL_free(path);
+    }
+    return res;
+}
+
+static int test_http_url_invalid_prefix(void)
+{
+    return test_http_url_invalid("htttps://1.2.3.4:65535/pkix");
+}
+
+static int test_http_url_invalid_port(void)
+{
+    return test_http_url_invalid("https://1.2.3.4:65536/pkix");
+}
+
+static int test_http_url_invalid_path(void)
+{
+    return test_http_url_invalid("https://[FF01::101]pkix");
+}
+
 static int test_http_get_x509(void)
 {
     return test_http_x509(1);
@@ -177,6 +243,12 @@ int setup_tests(void)
     if (!TEST_ptr((x509 = load_pem_cert(test_get_argument(0)))))
         return 1;
 
+    ADD_TEST(test_http_url_dns);
+    ADD_TEST(test_http_url_ipv4);
+    ADD_TEST(test_http_url_ipv6);
+    ADD_TEST(test_http_url_invalid_prefix);
+    ADD_TEST(test_http_url_invalid_port);
+    ADD_TEST(test_http_url_invalid_path);
     ADD_TEST(test_http_get_x509);
     ADD_TEST(test_http_post_x509);
     return 1;
diff --git a/test/recipes/81-test_cmp_cli_data/test_connection.csv b/test/recipes/81-test_cmp_cli_data/test_connection.csv
index 7e4775afec..5d1700fa21 100644
--- a/test/recipes/81-test_cmp_cli_data/test_connection.csv
+++ b/test/recipes/81-test_cmp_cli_data/test_connection.csv
@@ -22,6 +22,9 @@ TBD,server IP address with TLS port, -section,, -server,_SERVER_IP:_SERVER_TLS,,
 1,proxy default port, -section,, -server,_SERVER_HOST:_SERVER_PORT, -proxy,127.0.0.1,,,BLANK,,BLANK,,BLANK,,BLANK, -no_proxy,nonmatch.com,-msg_timeout,1
 1,proxy missing argument, -section,, -server,_SERVER_HOST:_SERVER_PORT, -proxy,,,,BLANK,,BLANK,,BLANK,,BLANK, -no_proxy,nonmatch.com
 ,,,,,,,,,,,,,,,,,,,,,,,,,
+0,path explicit, -section,, -server,_SERVER_HOST:_SERVER_PORT,,, -path,_SERVER_PATH,BLANK,,BLANK,,BLANK,,BLANK,
+0,path overrides -server path, -section,, -server,_SERVER_HOST:_SERVER_PORT/ignored,,, -path,_SERVER_PATH,BLANK,,BLANK,,BLANK,,BLANK,
+0,path default -server path, -section,, -server,_SERVER_HOST:_SERVER_PORT/_SERVER_PATH,,, -path,"""",BLANK,,BLANK,,BLANK,,BLANK,
 1,path missing argument, -section,,,,,, -path,,BLANK,,BLANK,,BLANK,,BLANK,
 1,path wrong, -section,,,,,, -path,/publicweb/cmp/example,BLANK,,BLANK,,BLANK,,BLANK,
 0,path with additional '/'s fine according to RFC 3986, -section,,,,,, -path,/_SERVER_PATH////,BLANK,,BLANK,,BLANK,,BLANK


More information about the openssl-commits mailing list