]> git.ipfire.org Git - thirdparty/openssl.git/blobdiff - crypto/http/http_client.c
Copyright year updates
[thirdparty/openssl.git] / crypto / http / http_client.c
index f786f831bf86711bf7218246aeb2c88b43243e75..9309954ef870a78a5cc9a95cdb1ba83aee545c3e 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2001-2021 The OpenSSL Project Authors. All Rights Reserved.
+ * Copyright 2001-2024 The OpenSSL Project Authors. All Rights Reserved.
  * Copyright Siemens AG 2018-2020
  *
  * Licensed under the Apache License 2.0 (the "License").  You may not use
@@ -8,7 +8,7 @@
  * https://www.openssl.org/source/license.html
  */
 
-#include "e_os.h"
+#include "internal/e_os.h"
 #include <stdio.h>
 #include <stdlib.h>
 #include "crypto/ctype.h"
@@ -20,6 +20,7 @@
 #include <openssl/cmperr.h>
 #include <openssl/buffer.h>
 #include <openssl/http.h>
+#include <openssl/trace.h>
 #include "internal/sockets.h"
 #include "internal/common.h" /* for ossl_assert() */
 
@@ -50,11 +51,12 @@ struct ossl_http_req_ctx_st {
     void *upd_arg;              /* Optional arg for update callback function */
     int use_ssl;                /* Use HTTPS */
     char *proxy;                /* Optional proxy name or URI */
-    char *server;               /* Optional server host name */
+    char *server;               /* Optional server hostname */
     char *port;                 /* Optional server port */
-    BIO *mem;                   /* Memory BIO holding request/response header */
+    BIO *mem;                   /* Mem BIO holding request header or response */
     BIO *req;                   /* BIO holding the request provided by caller */
     int method_POST;            /* HTTP method is POST (else GET) */
+    int text;                   /* Request content type is (likely) text */
     char *expected_ct;          /* Optional expected Content-Type */
     int expect_asn1;            /* Response must be ASN.1-encoded */
     unsigned char *pos;         /* Current position sending data */
@@ -65,6 +67,7 @@ struct ossl_http_req_ctx_st {
     time_t max_time;            /* Maximum end time of current transfer, or 0 */
     time_t max_total_time;      /* Maximum end time of total transfer, or 0 */
     char *redirection_url;      /* Location obtained from HTTP status 301/302 */
+    size_t max_hdr_lines;       /* Max. number of http hdr lines, or 0 */
 };
 
 /* HTTP states */
@@ -73,16 +76,18 @@ struct ossl_http_req_ctx_st {
 #define OHS_ERROR          (0 | OHS_NOREAD) /* Error condition */
 #define OHS_ADD_HEADERS    (1 | OHS_NOREAD) /* Adding header lines to request */
 #define OHS_WRITE_INIT     (2 | OHS_NOREAD) /* 1st call: ready to start send */
-#define OHS_WRITE_HDR      (3 | OHS_NOREAD) /* Request header being sent */
-#define OHS_WRITE_REQ      (4 | OHS_NOREAD) /* Request contents being sent */
-#define OHS_FLUSH          (5 | OHS_NOREAD) /* Request being flushed */
+#define OHS_WRITE_HDR1     (3 | OHS_NOREAD) /* Request header to be sent */
+#define OHS_WRITE_HDR      (4 | OHS_NOREAD) /* Request header being sent */
+#define OHS_WRITE_REQ      (5 | OHS_NOREAD) /* Request content being sent */
+#define OHS_FLUSH          (6 | OHS_NOREAD) /* Request being flushed */
 #define OHS_FIRSTLINE       1 /* First line of response being read */
 #define OHS_HEADERS         2 /* MIME headers of response being read */
-#define OHS_REDIRECT        3 /* MIME headers being read, expecting Location */
-#define OHS_ASN1_HEADER     4 /* ASN1 sequence header (tag+length) being read */
-#define OHS_ASN1_CONTENT    5 /* ASN1 content octets being read */
-#define OHS_ASN1_DONE      (6 | OHS_NOREAD) /* ASN1 content read completed */
-#define OHS_STREAM         (7 | OHS_NOREAD) /* HTTP content stream to be read */
+#define OHS_HEADERS_ERROR   3 /* MIME headers of resp. being read after error */
+#define OHS_REDIRECT        4 /* MIME headers being read, expecting Location */
+#define OHS_ASN1_HEADER     5 /* ASN1 sequence header (tag+length) being read */
+#define OHS_ASN1_CONTENT    6 /* ASN1 content octets being read */
+#define OHS_ASN1_DONE      (7 | OHS_NOREAD) /* ASN1 content read completed */
+#define OHS_STREAM         (8 | OHS_NOREAD) /* HTTP content stream to be read */
 
 /* Low-level HTTP API implementation */
 
@@ -102,6 +107,7 @@ OSSL_HTTP_REQ_CTX *OSSL_HTTP_REQ_CTX_new(BIO *wbio, BIO *rbio, int buf_size)
     rctx->buf = OPENSSL_malloc(rctx->buf_size);
     rctx->wbio = wbio;
     rctx->rbio = rbio;
+    rctx->max_hdr_lines = OSSL_HTTP_DEFAULT_MAX_RESP_HDR_LINES;
     if (rctx->buf == NULL) {
         OPENSSL_free(rctx);
         return NULL;
@@ -163,7 +169,8 @@ void OSSL_HTTP_REQ_CTX_set_max_response_length(OSSL_HTTP_REQ_CTX *rctx,
 
 /*
  * Create request line using |rctx| and |path| (or "/" in case |path| is NULL).
- * Server name (and port) must be given if and only if plain HTTP proxy is used.
+ * Server name (and optional port) must be given if and only if
+ * a plain HTTP proxy is used and |path| does not begin with 'http://'.
  */
 int OSSL_HTTP_REQ_CTX_set_request_line(OSSL_HTTP_REQ_CTX *rctx, int method_POST,
                                        const char *server, const char *port,
@@ -192,11 +199,17 @@ int OSSL_HTTP_REQ_CTX_set_request_line(OSSL_HTTP_REQ_CTX *rctx, int method_POST,
             return 0;
     }
 
-    /* Make sure path includes a forward slash */
-    if (path == NULL)
+    /* Make sure path includes a forward slash (abs_path) */
+    if (path == NULL)  {
         path = "/";
-    if (path[0] != '/' && BIO_printf(rctx->mem, "/") <= 0)
+    } else if (HAS_PREFIX(path, "http://")) { /* absoluteURI for proxy use */
+        if (server != NULL) {
+            ERR_raise(ERR_LIB_HTTP, ERR_R_PASSED_INVALID_ARGUMENT);
+            return 0;
+        }
+    } else if (path[0] != '/' && BIO_printf(rctx->mem, "/") <= 0) {
         return 0;
+    }
     /*
      * Add (the rest of) the path and the HTTP version,
      * which is fixed to 1.0 for straightforward implementation of keep-alive
@@ -265,7 +278,10 @@ int OSSL_HTTP_REQ_CTX_set_expected(OSSL_HTTP_REQ_CTX *rctx,
 static int set1_content(OSSL_HTTP_REQ_CTX *rctx,
                         const char *content_type, BIO *req)
 {
-    long req_len;
+    long req_len = 0;
+#ifndef OPENSSL_NO_STDIO
+    FILE *fp = NULL;
+#endif
 
     if (rctx == NULL || (req == NULL && content_type != NULL)) {
         ERR_raise(ERR_LIB_HTTP, ERR_R_PASSED_NULL_PARAMETER);
@@ -285,18 +301,47 @@ static int set1_content(OSSL_HTTP_REQ_CTX *rctx,
         return 0;
     }
 
-    if (content_type != NULL
-            && BIO_printf(rctx->mem, "Content-Type: %s\r\n", content_type) <= 0)
-        return 0;
+    if (content_type == NULL) {
+        rctx->text = 1; /* assuming text by default, used just for tracing */
+    } else {
+        if (OPENSSL_strncasecmp(content_type, "text/", 5) == 0)
+            rctx->text = 1;
+        if (BIO_printf(rctx->mem, "Content-Type: %s\r\n", content_type) <= 0)
+            return 0;
+    }
 
-    /* streaming BIO may not support querying size */
-    if (((req_len = BIO_ctrl(req, BIO_CTRL_INFO, 0, NULL)) <= 0
-         || BIO_printf(rctx->mem, "Content-Length: %ld\r\n", req_len) > 0)
-        && BIO_up_ref(req)) {
-        rctx->req = req;
-        return 1;
+    /*
+     * BIO_CTRL_INFO yields the data length at least for memory BIOs, but for
+     * file-based BIOs it gives the current position, which is not what we need.
+     */
+    if (BIO_method_type(req) == BIO_TYPE_FILE) {
+#ifndef OPENSSL_NO_STDIO
+        if (BIO_get_fp(req, &fp) == 1 && fseek(fp, 0, SEEK_END) == 0) {
+            req_len = ftell(fp);
+            (void)fseek(fp, 0, SEEK_SET);
+        } else {
+            fp = NULL;
+        }
+#endif
+    } else {
+        req_len = BIO_ctrl(req, BIO_CTRL_INFO, 0, NULL);
+        /*
+         * Streaming BIOs likely will not support querying the size at all,
+         * and we assume we got a correct value if req_len > 0.
+         */
     }
-    return 0;
+    if ((
+#ifndef OPENSSL_NO_STDIO
+         fp != NULL /* definitely correct req_len */ ||
+#endif
+         req_len > 0)
+            && BIO_printf(rctx->mem, "Content-Length: %ld\r\n", req_len) < 0)
+        return 0;
+
+    if (!BIO_up_ref(req))
+        return 0;
+    rctx->req = req;
+    return 1;
 }
 
 int OSSL_HTTP_REQ_CTX_set1_req(OSSL_HTTP_REQ_CTX *rctx, const char *content_type,
@@ -312,6 +357,16 @@ int OSSL_HTTP_REQ_CTX_set1_req(OSSL_HTTP_REQ_CTX *rctx, const char *content_type
     return res;
 }
 
+void OSSL_HTTP_REQ_CTX_set_max_response_hdr_lines(OSSL_HTTP_REQ_CTX *rctx,
+                                                  size_t count)
+{
+    if (rctx == NULL) {
+        ERR_raise(ERR_LIB_HTTP, ERR_R_PASSED_NULL_PARAMETER);
+        return;
+    }
+    rctx->max_hdr_lines = count;
+}
+
 static int add1_headers(OSSL_HTTP_REQ_CTX *rctx,
                         const STACK_OF(CONF_VALUE) *headers, const char *host)
 {
@@ -321,7 +376,7 @@ static int add1_headers(OSSL_HTTP_REQ_CTX *rctx,
 
     for (i = 0; i < sk_CONF_VALUE_num(headers); i++) {
         hdr = sk_CONF_VALUE_value(headers, i);
-        if (add_host && strcasecmp("host", hdr->name) == 0)
+        if (add_host && OPENSSL_strcasecmp("host", hdr->name) == 0)
             add_host = 0;
         if (!OSSL_HTTP_REQ_CTX_add1_header(rctx, hdr->name, hdr->value))
             return 0;
@@ -452,13 +507,17 @@ static int parse_http_line1(char *line, int *found_keep_alive)
 
 static int check_set_resp_len(OSSL_HTTP_REQ_CTX *rctx, size_t len)
 {
-    if (rctx->max_resp_len != 0 && len > rctx->max_resp_len)
+    if (rctx->max_resp_len != 0 && len > rctx->max_resp_len) {
         ERR_raise_data(ERR_LIB_HTTP, HTTP_R_MAX_RESP_LEN_EXCEEDED,
                        "length=%zu, max=%zu", len, rctx->max_resp_len);
-    if (rctx->resp_len != 0 && rctx->resp_len != len)
+        return 0;
+    }
+    if (rctx->resp_len != 0 && rctx->resp_len != len) {
         ERR_raise_data(ERR_LIB_HTTP, HTTP_R_INCONSISTENT_CONTENT_LENGTH,
                        "ASN.1 length=%zu, Content-Length=%zu",
                        len, rctx->resp_len);
+        return 0;
+    }
     rctx->resp_len = len;
     return 1;
 }
@@ -485,10 +544,12 @@ static int may_still_retry(time_t max_time, int *ptimeout)
 int OSSL_HTTP_REQ_CTX_nbio(OSSL_HTTP_REQ_CTX *rctx)
 {
     int i, found_expected_ct = 0, found_keep_alive = 0;
+    int got_text = 1;
     long n;
     size_t resp_len;
     const unsigned char *p;
     char *buf, *key, *value, *line_end = NULL;
+    size_t resp_hdr_lines = 0;
 
     if (rctx == NULL) {
         ERR_raise(ERR_LIB_HTTP, ERR_R_PASSED_NULL_PARAMETER);
@@ -536,27 +597,35 @@ int OSSL_HTTP_REQ_CTX_nbio(OSSL_HTTP_REQ_CTX *rctx)
         }
         rctx->state = OHS_WRITE_INIT;
 
-        /* fall thru */
+        /* fall through */
     case OHS_WRITE_INIT:
         rctx->len_to_send = BIO_get_mem_data(rctx->mem, &rctx->pos);
-        rctx->state = OHS_WRITE_HDR;
+        rctx->state = OHS_WRITE_HDR1;
 
-        /* fall thru */
+        /* fall through */
+    case OHS_WRITE_HDR1:
     case OHS_WRITE_HDR:
         /* Copy some chunk of data from rctx->mem to rctx->wbio */
     case OHS_WRITE_REQ:
         /* Copy some chunk of data from rctx->req to rctx->wbio */
 
         if (rctx->len_to_send > 0) {
-            i = BIO_write(rctx->wbio, rctx->pos, rctx->len_to_send);
-            if (i <= 0) {
+            size_t sz;
+
+            if (!BIO_write_ex(rctx->wbio, rctx->pos, rctx->len_to_send, &sz)) {
                 if (BIO_should_retry(rctx->wbio))
                     return -1;
                 rctx->state = OHS_ERROR;
                 return 0;
             }
-            rctx->pos += i;
-            rctx->len_to_send -= i;
+            if (OSSL_TRACE_ENABLED(HTTP) && rctx->state == OHS_WRITE_HDR1)
+                OSSL_TRACE(HTTP, "Sending request: [\n");
+            OSSL_TRACE_STRING(HTTP, rctx->state != OHS_WRITE_REQ || rctx->text,
+                              rctx->state != OHS_WRITE_REQ, rctx->pos, sz);
+            if (rctx->state == OHS_WRITE_HDR1)
+                rctx->state = OHS_WRITE_HDR;
+            rctx->pos += sz;
+            rctx->len_to_send -= sz;
             goto next_io;
         }
         if (rctx->state == OHS_WRITE_HDR) {
@@ -566,7 +635,7 @@ int OSSL_HTTP_REQ_CTX_nbio(OSSL_HTTP_REQ_CTX *rctx)
         if (rctx->req != NULL && !BIO_eof(rctx->req)) {
             n = BIO_read(rctx->req, rctx->buf, rctx->buf_size);
             if (n <= 0) {
-                if (BIO_should_retry(rctx->rbio))
+                if (BIO_should_retry(rctx->req))
                     return -1;
                 ERR_raise(ERR_LIB_HTTP, HTTP_R_FAILED_READING_DATA);
                 return 0;
@@ -575,9 +644,11 @@ int OSSL_HTTP_REQ_CTX_nbio(OSSL_HTTP_REQ_CTX *rctx)
             rctx->len_to_send = n;
             goto next_io;
         }
+        if (OSSL_TRACE_ENABLED(HTTP))
+            OSSL_TRACE(HTTP, "]\n");
         rctx->state = OHS_FLUSH;
 
-        /* fall thru */
+        /* fall through */
     case OHS_FLUSH:
 
         i = BIO_flush(rctx->wbio);
@@ -624,6 +695,14 @@ int OSSL_HTTP_REQ_CTX_nbio(OSSL_HTTP_REQ_CTX *rctx)
             return 0;
         }
 
+        resp_hdr_lines++;
+        if (rctx->max_hdr_lines != 0 && rctx->max_hdr_lines < resp_hdr_lines) {
+            ERR_raise(ERR_LIB_HTTP, HTTP_R_RESPONSE_TOO_MANY_HDRLINES);
+            OSSL_TRACE(HTTP, "Received too many headers\n");
+            rctx->state = OHS_ERROR;
+            return 0;
+        }
+
         /* Don't allow excessive lines */
         if (n == rctx->buf_size) {
             ERR_raise(ERR_LIB_HTTP, HTTP_R_RESPONSE_LINE_TOO_LONG);
@@ -631,6 +710,13 @@ int OSSL_HTTP_REQ_CTX_nbio(OSSL_HTTP_REQ_CTX *rctx)
             return 0;
         }
 
+        /* dump all response header lines */
+        if (OSSL_TRACE_ENABLED(HTTP)) {
+            if (rctx->state == OHS_FIRSTLINE)
+                OSSL_TRACE(HTTP, "Received response header: [\n");
+            OSSL_TRACE1(HTTP, "%s", buf);
+        }
+
         /* First line */
         if (rctx->state == OHS_FIRSTLINE) {
             switch (parse_http_line1(buf, &found_keep_alive)) {
@@ -647,8 +733,8 @@ int OSSL_HTTP_REQ_CTX_nbio(OSSL_HTTP_REQ_CTX *rctx)
                 /* redirection is not supported/recommended for POST */
                 /* fall through */
             default:
-                rctx->state = OHS_ERROR;
-                goto next_line;
+                rctx->state = OHS_HEADERS_ERROR;
+                goto next_line; /* continue parsing and reporting header */
             }
         }
         key = buf;
@@ -665,28 +751,40 @@ int OSSL_HTTP_REQ_CTX_nbio(OSSL_HTTP_REQ_CTX *rctx)
         }
         if (value != NULL && line_end != NULL) {
             if (rctx->state == OHS_REDIRECT
-                    && strcasecmp(key, "Location") == 0) {
+                    && OPENSSL_strcasecmp(key, "Location") == 0) {
                 rctx->redirection_url = value;
                 return 0;
             }
-            if (rctx->expected_ct != NULL
-                    && strcasecmp(key, "Content-Type") == 0) {
-                if (strcasecmp(rctx->expected_ct, value) != 0) {
-                    ERR_raise_data(ERR_LIB_HTTP, HTTP_R_UNEXPECTED_CONTENT_TYPE,
-                                   "expected=%s, actual=%s",
-                                   rctx->expected_ct, value);
-                    return 0;
+            if (OPENSSL_strcasecmp(key, "Content-Type") == 0) {
+                got_text = OPENSSL_strncasecmp(value, "text/", 5) == 0;
+                if (rctx->state == OHS_HEADERS
+                    && rctx->expected_ct != NULL) {
+                    const char *semicolon;
+
+                    if (OPENSSL_strcasecmp(rctx->expected_ct, value) != 0
+                        /* ignore past ';' unless expected_ct contains ';' */
+                        && (strchr(rctx->expected_ct, ';') != NULL
+                            || (semicolon = strchr(value, ';')) == NULL
+                            || (size_t)(semicolon - value) != strlen(rctx->expected_ct)
+                            || OPENSSL_strncasecmp(rctx->expected_ct, value,
+                                                   semicolon - value) != 0)) {
+                        ERR_raise_data(ERR_LIB_HTTP,
+                                       HTTP_R_UNEXPECTED_CONTENT_TYPE,
+                                       "expected=%s, actual=%s",
+                                       rctx->expected_ct, value);
+                        return 0;
+                    }
+                    found_expected_ct = 1;
                 }
-                found_expected_ct = 1;
             }
 
             /* https://tools.ietf.org/html/rfc7230#section-6.3 Persistence */
-            if (strcasecmp(key, "Connection") == 0) {
-                if (strcasecmp(value, "keep-alive") == 0)
+            if (OPENSSL_strcasecmp(key, "Connection") == 0) {
+                if (OPENSSL_strcasecmp(value, "keep-alive") == 0)
                     found_keep_alive = 1;
-                else if (strcasecmp(value, "close") == 0)
+                else if (OPENSSL_strcasecmp(value, "close") == 0)
                     found_keep_alive = 0;
-            } else if (strcasecmp(key, "Content-Length") == 0) {
+            } else if (OPENSSL_strcasecmp(key, "Content-Length") == 0) {
                 resp_len = (size_t)strtoul(value, &line_end, 10);
                 if (line_end == value || *line_end != '\0') {
                     ERR_raise_data(ERR_LIB_HTTP,
@@ -706,6 +804,10 @@ int OSSL_HTTP_REQ_CTX_nbio(OSSL_HTTP_REQ_CTX *rctx)
         }
         if (*p != '\0') /* not end of headers */
             goto next_line;
+        if (OSSL_TRACE_ENABLED(HTTP))
+            OSSL_TRACE(HTTP, "]\n");
+
+        resp_hdr_lines = 0;
 
         if (rctx->keep_alive != 0 /* do not let server initiate keep_alive */
                 && !found_keep_alive /* otherwise there is no change */) {
@@ -717,8 +819,22 @@ int OSSL_HTTP_REQ_CTX_nbio(OSSL_HTTP_REQ_CTX *rctx)
             rctx->keep_alive = 0;
         }
 
-        if (rctx->state == OHS_ERROR)
+        if (rctx->state == OHS_HEADERS_ERROR) {
+            if (OSSL_TRACE_ENABLED(HTTP)) {
+                int printed_final_nl = 0;
+
+                OSSL_TRACE(HTTP, "Received error response body: [\n");
+                while ((n = BIO_read(rctx->rbio, rctx->buf, rctx->buf_size)) > 0
+                       || (OSSL_sleep(100), BIO_should_retry(rctx->rbio))) {
+                    OSSL_TRACE_STRING(HTTP, got_text, 1, rctx->buf, n);
+                    if (n > 0)
+                        printed_final_nl = rctx->buf[n - 1] == '\n';
+                }
+                OSSL_TRACE1(HTTP, "%s]\n", printed_final_nl ? "" : "\n");
+                (void)printed_final_nl; /* avoid warning unless enable-trace */
+            }
             return 0;
+        }
 
         if (rctx->expected_ct != NULL && !found_expected_ct) {
             ERR_raise_data(ERR_LIB_HTTP, HTTP_R_MISSING_CONTENT_TYPE,
@@ -946,11 +1062,12 @@ OSSL_HTTP_REQ_CTX *OSSL_HTTP_open(const char *server, const char *port,
     }
     /* now overall_timeout is guaranteed to be >= 0 */
 
+    /* adapt in order to fix callback design flaw, see #17088 */
     /* callback can be used to wrap or prepend TLS session */
     if (bio_update_fn != NULL) {
         BIO *orig_bio = cbio;
 
-        cbio = (*bio_update_fn)(cbio, arg, 1 /* connect */, use_ssl);
+        cbio = (*bio_update_fn)(cbio, arg, 1 /* connect */, use_ssl != 0);
         if (cbio == NULL) {
             if (bio == NULL) /* cbio was not provided by caller */
                 BIO_free_all(orig_bio);
@@ -1087,13 +1204,12 @@ BIO *OSSL_HTTP_get(const char *url, const char *proxy, const char *no_proxy,
                    const char *expected_ct, int expect_asn1,
                    size_t max_resp_len, int timeout)
 {
-    char *current_url, *redirection_url = NULL;
+    char *current_url;
     int n_redirs = 0;
     char *host;
     char *port;
     char *path;
     int use_ssl;
-    OSSL_HTTP_REQ_CTX *rctx;
     BIO *resp = NULL;
     time_t max_time = timeout > 0 ? time(NULL) + timeout : 0;
 
@@ -1105,6 +1221,9 @@ BIO *OSSL_HTTP_get(const char *url, const char *proxy, const char *no_proxy,
         return NULL;
 
     for (;;) {
+        OSSL_HTTP_REQ_CTX *rctx;
+        char *redirection_url;
+
         if (!OSSL_HTTP_parse_url(current_url, &use_ssl, NULL /* user */, &host,
                                  &port, NULL /* port_num */, &path, NULL, NULL))
             break;
@@ -1113,16 +1232,19 @@ BIO *OSSL_HTTP_get(const char *url, const char *proxy, const char *no_proxy,
                               use_ssl, bio, rbio, bio_update_fn, arg,
                               buf_size, timeout);
     new_rpath:
+        redirection_url = NULL;
         if (rctx != NULL) {
             if (!OSSL_HTTP_set1_request(rctx, path, headers,
                                         NULL /* content_type */,
                                         NULL /* req */,
                                         expected_ct, expect_asn1, max_resp_len,
                                         -1 /* use same max time (timeout) */,
-                                        0 /* no keep_alive */))
+                                        0 /* no keep_alive */)) {
                 OSSL_HTTP_REQ_CTX_free(rctx);
-            else
+                rctx = NULL;
+            } else {
                 resp = OSSL_HTTP_exchange(rctx, &redirection_url);
+            }
         }
         OPENSSL_free(path);
         if (resp == NULL && redirection_url != NULL) {
@@ -1133,6 +1255,14 @@ BIO *OSSL_HTTP_get(const char *url, const char *proxy, const char *no_proxy,
                 current_url = redirection_url;
                 if (*redirection_url == '/') { /* redirection to same server */
                     path = OPENSSL_strdup(redirection_url);
+                    if (path == NULL) {
+                        OPENSSL_free(host);
+                        OPENSSL_free(port);
+                        (void)OSSL_HTTP_close(rctx, 1);
+                        BIO_free(resp);
+                        OPENSSL_free(current_url);
+                        return NULL;
+                    }
                     goto new_rpath;
                 }
                 OPENSSL_free(host);
@@ -1370,7 +1500,7 @@ int OSSL_HTTP_proxy_connect(BIO *bio, const char *server, const char *port,
     do {
         /*
          * This does not necessarily catch the case when the full
-         * HTTP response came in in more than a single TCP message.
+         * HTTP response came in more than a single TCP message.
          */
         read_len = BIO_gets(fbio, mbuf, BUF_SIZE);
     } while (read_len > 2);