summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Stenberg <daniel@haxx.se>2022-02-06 17:52:16 +0100
committerDaniel Stenberg <daniel@haxx.se>2022-02-07 13:56:06 +0100
commitf8c3724aa90472c0e617ddbbc420aa199971eb77 (patch)
treeb4778cc1fccae99b15c2aab21f28e7e949c6d268
parent79731d1a6cfbab62297bc3d29788b8d6c8e9a0cb (diff)
downloadcurl-f8c3724aa90472c0e617ddbbc420aa199971eb77.tar.gz
h2h3: added Curl_pseudo_headers()
For use with both http2 and http3 requests.
-rw-r--r--lib/Makefile.inc2
-rw-r--r--lib/h2h3.c310
-rw-r--r--lib/h2h3.h59
3 files changed, 371 insertions, 0 deletions
diff --git a/lib/Makefile.inc b/lib/Makefile.inc
index 3c9dbc8e7..e8f110fee 100644
--- a/lib/Makefile.inc
+++ b/lib/Makefile.inc
@@ -139,6 +139,7 @@ LIB_CFILES = \
getenv.c \
getinfo.c \
gopher.c \
+ h2h3.c \
hash.c \
hmac.c \
hostasyn.c \
@@ -267,6 +268,7 @@ LIB_HFILES = \
ftplistparser.h \
getinfo.h \
gopher.h \
+ h2h3.h \
hash.h \
hostip.h \
hsts.h \
diff --git a/lib/h2h3.c b/lib/h2h3.c
new file mode 100644
index 000000000..04c08863d
--- /dev/null
+++ b/lib/h2h3.c
@@ -0,0 +1,310 @@
+/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ***************************************************************************/
+
+#include "curl_setup.h"
+#include "urldata.h"
+#include "h2h3.h"
+#include "transfer.h"
+#include "sendf.h"
+#include "strcase.h"
+
+/* The last 3 #include files should be in this order */
+#include "curl_printf.h"
+#include "curl_memory.h"
+#include "memdebug.h"
+
+/*
+ * Curl_pseudo_headers() creates the array with pseudo headers to be
+ * used in a HTTP/2 or HTTP/3 request.
+ */
+
+#if defined(USE_NGHTTP2) || defined(USE_HTTP3)
+
+/* Index where :authority header field will appear in request header
+ field list. */
+#define AUTHORITY_DST_IDX 3
+
+/* USHRT_MAX is 65535 == 0xffff */
+#define HEADER_OVERFLOW(x) \
+ (x.namelen > 0xffff || x.valuelen > 0xffff - x.namelen)
+
+/*
+ * Check header memory for the token "trailers".
+ * Parse the tokens as separated by comma and surrounded by whitespace.
+ * Returns TRUE if found or FALSE if not.
+ */
+static bool contains_trailers(const char *p, size_t len)
+{
+ const char *end = p + len;
+ for(;;) {
+ for(; p != end && (*p == ' ' || *p == '\t'); ++p)
+ ;
+ if(p == end || (size_t)(end - p) < sizeof("trailers") - 1)
+ return FALSE;
+ if(strncasecompare("trailers", p, sizeof("trailers") - 1)) {
+ p += sizeof("trailers") - 1;
+ for(; p != end && (*p == ' ' || *p == '\t'); ++p)
+ ;
+ if(p == end || *p == ',')
+ return TRUE;
+ }
+ /* skip to next token */
+ for(; p != end && *p != ','; ++p)
+ ;
+ if(p == end)
+ return FALSE;
+ ++p;
+ }
+}
+
+typedef enum {
+ /* Send header to server */
+ HEADERINST_FORWARD,
+ /* Don't send header to server */
+ HEADERINST_IGNORE,
+ /* Discard header, and replace it with "te: trailers" */
+ HEADERINST_TE_TRAILERS
+} header_instruction;
+
+/* Decides how to treat given header field. */
+static header_instruction inspect_header(const char *name, size_t namelen,
+ const char *value, size_t valuelen) {
+ switch(namelen) {
+ case 2:
+ if(!strncasecompare("te", name, namelen))
+ return HEADERINST_FORWARD;
+
+ return contains_trailers(value, valuelen) ?
+ HEADERINST_TE_TRAILERS : HEADERINST_IGNORE;
+ case 7:
+ return strncasecompare("upgrade", name, namelen) ?
+ HEADERINST_IGNORE : HEADERINST_FORWARD;
+ case 10:
+ return (strncasecompare("connection", name, namelen) ||
+ strncasecompare("keep-alive", name, namelen)) ?
+ HEADERINST_IGNORE : HEADERINST_FORWARD;
+ case 16:
+ return strncasecompare("proxy-connection", name, namelen) ?
+ HEADERINST_IGNORE : HEADERINST_FORWARD;
+ case 17:
+ return strncasecompare("transfer-encoding", name, namelen) ?
+ HEADERINST_IGNORE : HEADERINST_FORWARD;
+ default:
+ return HEADERINST_FORWARD;
+ }
+}
+
+CURLcode Curl_pseudo_headers(struct Curl_easy *data,
+ const char *mem, /* the requeset */
+ const size_t len /* size of request */,
+ struct h2h3req **hp)
+{
+ struct connectdata *conn = data->conn;
+ size_t nheader = 0;
+ size_t i;
+ size_t authority_idx;
+ char *hdbuf = (char *)mem;
+ char *end, *line_end;
+ struct h2h3pseudo *nva = NULL;
+ struct h2h3req *hreq = NULL;
+ char *vptr;
+
+ /* Calculate number of headers contained in [mem, mem + len). Assumes a
+ correctly generated HTTP header field block. */
+ for(i = 1; i < len; ++i) {
+ if(hdbuf[i] == '\n' && hdbuf[i - 1] == '\r') {
+ ++nheader;
+ ++i;
+ }
+ }
+ if(nheader < 2) {
+ goto fail;
+ }
+ /* We counted additional 2 \r\n in the first and last line. We need 3
+ new headers: :method, :path and :scheme. Therefore we need one
+ more space. */
+ nheader += 1;
+ hreq = malloc(sizeof(struct h2h3req) +
+ sizeof(struct h2h3pseudo) * (nheader - 1));
+ if(!hreq) {
+ goto fail;
+ }
+
+ nva = &hreq->header[0];
+
+ /* Extract :method, :path from request line
+ We do line endings with CRLF so checking for CR is enough */
+ line_end = memchr(hdbuf, '\r', len);
+ if(!line_end) {
+ goto fail;
+ }
+
+ /* Method does not contain spaces */
+ end = memchr(hdbuf, ' ', line_end - hdbuf);
+ if(!end || end == hdbuf)
+ goto fail;
+ nva[0].name = H2H3_PSEUDO_METHOD;
+ nva[0].namelen = sizeof(H2H3_PSEUDO_METHOD) - 1;
+ nva[0].value = hdbuf;
+ nva[0].valuelen = (size_t)(end - hdbuf);
+
+ hdbuf = end + 1;
+
+ /* Path may contain spaces so scan backwards */
+ end = NULL;
+ for(i = (size_t)(line_end - hdbuf); i; --i) {
+ if(hdbuf[i - 1] == ' ') {
+ end = &hdbuf[i - 1];
+ break;
+ }
+ }
+ if(!end || end == hdbuf)
+ goto fail;
+ nva[1].name = H2H3_PSEUDO_PATH;
+ nva[1].namelen = sizeof(H2H3_PSEUDO_PATH) - 1;
+ nva[1].value = hdbuf;
+ nva[1].valuelen = (end - hdbuf);
+
+ nva[2].name = H2H3_PSEUDO_SCHEME;
+ nva[2].namelen = sizeof(H2H3_PSEUDO_SCHEME) - 1;
+ vptr = Curl_checkheaders(data, H2H3_PSEUDO_SCHEME);
+ if(vptr) {
+ vptr += sizeof(H2H3_PSEUDO_SCHEME);
+ while(*vptr && ISSPACE(*vptr))
+ vptr++;
+ nva[2].value = vptr;
+ infof(data, "set pseduo header %s to %s", H2H3_PSEUDO_SCHEME, vptr);
+ }
+ else {
+ if(conn->handler->flags & PROTOPT_SSL)
+ nva[2].value = "https";
+ else
+ nva[2].value = "http";
+ }
+ nva[2].valuelen = strlen((char *)nva[2].value);
+
+ authority_idx = 0;
+ i = 3;
+ while(i < nheader) {
+ size_t hlen;
+
+ hdbuf = line_end + 2;
+
+ /* check for next CR, but only within the piece of data left in the given
+ buffer */
+ line_end = memchr(hdbuf, '\r', len - (hdbuf - (char *)mem));
+ if(!line_end || (line_end == hdbuf))
+ goto fail;
+
+ /* header continuation lines are not supported */
+ if(*hdbuf == ' ' || *hdbuf == '\t')
+ goto fail;
+
+ for(end = hdbuf; end < line_end && *end != ':'; ++end)
+ ;
+ if(end == hdbuf || end == line_end)
+ goto fail;
+ hlen = end - hdbuf;
+
+ if(hlen == 4 && strncasecompare("host", hdbuf, 4)) {
+ authority_idx = i;
+ nva[i].name = H2H3_PSEUDO_AUTHORITY;
+ nva[i].namelen = sizeof(H2H3_PSEUDO_AUTHORITY) - 1;
+ }
+ else {
+ nva[i].namelen = (size_t)(end - hdbuf);
+ /* Lower case the header name for HTTP/3 */
+ Curl_strntolower((char *)hdbuf, hdbuf, nva[i].namelen);
+ nva[i].name = hdbuf;
+ }
+ hdbuf = end + 1;
+ while(*hdbuf == ' ' || *hdbuf == '\t')
+ ++hdbuf;
+ end = line_end;
+
+ switch(inspect_header((const char *)nva[i].name, nva[i].namelen, hdbuf,
+ end - hdbuf)) {
+ case HEADERINST_IGNORE:
+ /* skip header fields prohibited by HTTP/2 specification. */
+ --nheader;
+ continue;
+ case HEADERINST_TE_TRAILERS:
+ nva[i].value = "trailers";
+ nva[i].valuelen = sizeof("trailers") - 1;
+ break;
+ default:
+ nva[i].value = hdbuf;
+ nva[i].valuelen = (end - hdbuf);
+ }
+
+ nva[i].value = hdbuf;
+ nva[i].valuelen = (end - hdbuf);
+
+ ++i;
+ }
+
+ /* :authority must come before non-pseudo header fields */
+ if(authority_idx && authority_idx != AUTHORITY_DST_IDX) {
+ struct h2h3pseudo authority = nva[authority_idx];
+ for(i = authority_idx; i > AUTHORITY_DST_IDX; --i) {
+ nva[i] = nva[i - 1];
+ }
+ nva[i] = authority;
+ }
+
+ /* Warn stream may be rejected if cumulative length of headers is too
+ large. */
+#define MAX_ACC 60000 /* <64KB to account for some overhead */
+ {
+ size_t acc = 0;
+
+ for(i = 0; i < nheader; ++i) {
+ acc += nva[i].namelen + nva[i].valuelen;
+
+ infof(data, "h2h3 [%.*s: %.*s]",
+ nva[i].namelen, nva[i].name,
+ nva[i].valuelen, nva[i].value);
+ }
+
+ if(acc > MAX_ACC) {
+ infof(data, "http_request: Warning: The cumulative length of all "
+ "headers exceeds %d bytes and that could cause the "
+ "stream to be rejected.", MAX_ACC);
+ }
+ }
+
+ hreq->entries = nheader;
+ *hp = hreq;
+
+ return CURLE_OK;
+
+ fail:
+ free(hreq);
+ return CURLE_OUT_OF_MEMORY;
+}
+
+void Curl_pseudo_free(struct h2h3req *hp)
+{
+ free(hp);
+}
+
+#endif /* USE_NGHTTP2 or HTTP/3 enabled */
diff --git a/lib/h2h3.h b/lib/h2h3.h
new file mode 100644
index 000000000..ec687663b
--- /dev/null
+++ b/lib/h2h3.h
@@ -0,0 +1,59 @@
+#ifndef HEADER_CURL_H2H3_H
+#define HEADER_CURL_H2H3_H
+/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ***************************************************************************/
+#include "curl_setup.h"
+
+#define H2H3_PSEUDO_METHOD ":method"
+#define H2H3_PSEUDO_SCHEME ":scheme"
+#define H2H3_PSEUDO_AUTHORITY ":authority"
+#define H2H3_PSEUDO_PATH ":path"
+#define H2H3_PSEUDO_STATUS ":status"
+
+struct h2h3pseudo {
+ const char *name;
+ size_t namelen;
+ const char *value;
+ size_t valuelen;
+};
+
+struct h2h3req {
+ size_t entries;
+ struct h2h3pseudo header[1]; /* the array is allocated to contain entries */
+};
+
+/*
+ * Curl_pseudo_headers() creates the array with pseudo headers to be
+ * used in a HTTP/2 or HTTP/3 request. Returns an allocated struct.
+ * Free it with Curl_pseudo_free().
+ */
+CURLcode Curl_pseudo_headers(struct Curl_easy *data,
+ const char *request,
+ size_t len,
+ struct h2h3req **hp);
+
+/*
+ * Curl_pseudo_free() frees a h2h3req struct.
+ */
+void Curl_pseudo_free(struct h2h3req *hp);
+
+#endif /* HEADER_CURL_H2H3_H */