/*
- * 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
* 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"
#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() */
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 */
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 */
#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 */
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;
/*
* 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,
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
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);
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,
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)
{
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;
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;
}
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);
}
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) {
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;
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);
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);
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)) {
/* 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;
}
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,
}
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 */) {
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,
}
/* 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);
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;
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;
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) {
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);
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);