http2: use Curl_pseudo_headers

This commit is contained in:
Daniel Stenberg 2022-02-06 18:04:19 +01:00
parent f8c3724aa9
commit 9f985a11e7
No known key found for this signature in database
GPG Key ID: 5CC908FDB71E12C2

View File

@ -38,6 +38,7 @@
#include "strdup.h" #include "strdup.h"
#include "transfer.h" #include "transfer.h"
#include "dynbuf.h" #include "dynbuf.h"
#include "h2h3.h"
/* The last 3 #include files should be in this order */ /* The last 3 #include files should be in this order */
#include "curl_printf.h" #include "curl_printf.h"
#include "curl_memory.h" #include "curl_memory.h"
@ -65,12 +66,6 @@
#define H2BUGF(x) do { } while(0) #define H2BUGF(x) do { } while(0)
#endif #endif
#define H2_PSEUDO_METHOD ":method"
#define H2_PSEUDO_SCHEME ":scheme"
#define H2_PSEUDO_AUTHORITY ":authority"
#define H2_PSEUDO_PATH ":path"
#define H2_PSEUDO_STATUS ":status"
static ssize_t http2_recv(struct Curl_easy *data, int sockindex, static ssize_t http2_recv(struct Curl_easy *data, int sockindex,
char *mem, size_t len, CURLcode *err); char *mem, size_t len, CURLcode *err);
static bool http2_connisdead(struct Curl_easy *data, static bool http2_connisdead(struct Curl_easy *data,
@ -519,7 +514,7 @@ static int set_transfer_url(struct Curl_easy *data,
if(!u) if(!u)
return 5; return 5;
v = curl_pushheader_byname(hp, H2_PSEUDO_SCHEME); v = curl_pushheader_byname(hp, H2H3_PSEUDO_SCHEME);
if(v) { if(v) {
uc = curl_url_set(u, CURLUPART_SCHEME, v, 0); uc = curl_url_set(u, CURLUPART_SCHEME, v, 0);
if(uc) { if(uc) {
@ -528,7 +523,7 @@ static int set_transfer_url(struct Curl_easy *data,
} }
} }
v = curl_pushheader_byname(hp, H2_PSEUDO_AUTHORITY); v = curl_pushheader_byname(hp, H2H3_PSEUDO_AUTHORITY);
if(v) { if(v) {
uc = curl_url_set(u, CURLUPART_HOST, v, 0); uc = curl_url_set(u, CURLUPART_HOST, v, 0);
if(uc) { if(uc) {
@ -537,7 +532,7 @@ static int set_transfer_url(struct Curl_easy *data,
} }
} }
v = curl_pushheader_byname(hp, H2_PSEUDO_PATH); v = curl_pushheader_byname(hp, H2H3_PSEUDO_PATH);
if(v) { if(v) {
uc = curl_url_set(u, CURLUPART_PATH, v, 0); uc = curl_url_set(u, CURLUPART_PATH, v, 0);
if(uc) { if(uc) {
@ -1015,7 +1010,7 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame,
if(frame->hd.type == NGHTTP2_PUSH_PROMISE) { if(frame->hd.type == NGHTTP2_PUSH_PROMISE) {
char *h; char *h;
if(!strcmp(H2_PSEUDO_AUTHORITY, (const char *)name)) { if(!strcmp(H2H3_PSEUDO_AUTHORITY, (const char *)name)) {
/* pseudo headers are lower case */ /* pseudo headers are lower case */
int rc = 0; int rc = 0;
char *check = aprintf("%s:%d", conn->host.name, conn->remote_port); char *check = aprintf("%s:%d", conn->host.name, conn->remote_port);
@ -1078,8 +1073,8 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame,
return 0; return 0;
} }
if(namelen == sizeof(H2_PSEUDO_STATUS) - 1 && if(namelen == sizeof(H2H3_PSEUDO_STATUS) - 1 &&
memcmp(H2_PSEUDO_STATUS, name, namelen) == 0) { memcmp(H2H3_PSEUDO_STATUS, name, namelen) == 0) {
/* nghttp2 guarantees :status is received first and only once, and /* nghttp2 guarantees :status is received first and only once, and
value is 3 digits status code, and decode_status_code always value is 3 digits status code, and decode_status_code always
succeeds. */ succeeds. */
@ -1822,80 +1817,6 @@ static ssize_t http2_recv(struct Curl_easy *data, int sockindex,
return -1; return -1;
} }
/* 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;
}
}
static ssize_t http2_send(struct Curl_easy *data, int sockindex, static ssize_t http2_send(struct Curl_easy *data, int sockindex,
const void *mem, size_t len, CURLcode *err) const void *mem, size_t len, CURLcode *err)
{ {
@ -1910,15 +1831,12 @@ static ssize_t http2_send(struct Curl_easy *data, int sockindex,
struct HTTP *stream = data->req.p.http; struct HTTP *stream = data->req.p.http;
nghttp2_nv *nva = NULL; nghttp2_nv *nva = NULL;
size_t nheader; size_t nheader;
size_t i;
size_t authority_idx;
char *hdbuf = (char *)mem;
char *end, *line_end;
nghttp2_data_provider data_prd; nghttp2_data_provider data_prd;
int32_t stream_id; int32_t stream_id;
nghttp2_session *h2 = httpc->h2; nghttp2_session *h2 = httpc->h2;
nghttp2_priority_spec pri_spec; nghttp2_priority_spec pri_spec;
char *vptr; CURLcode result;
struct h2h3req *hreq;
(void)sockindex; (void)sockindex;
@ -1984,185 +1902,28 @@ static ssize_t http2_send(struct Curl_easy *data, int sockindex,
return len; return len;
} }
/* Calculate number of headers contained in [mem, mem + len) */ result = Curl_pseudo_headers(data, mem, len, &hreq);
/* Here, we assume the curl http code generate *correct* HTTP header if(result) {
field block */ *err = result;
nheader = 0; return -1;
for(i = 1; i < len; ++i) {
if(hdbuf[i] == '\n' && hdbuf[i - 1] == '\r') {
++nheader;
++i;
}
} }
if(nheader < 2) nheader = hreq->entries;
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;
nva = malloc(sizeof(nghttp2_nv) * nheader); nva = malloc(sizeof(nghttp2_nv) * nheader);
if(!nva) { if(!nva) {
Curl_pseudo_free(hreq);
*err = CURLE_OUT_OF_MEMORY; *err = CURLE_OUT_OF_MEMORY;
return -1; return -1;
} }
/* 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 = (unsigned char *)H2_PSEUDO_METHOD;
nva[0].namelen = sizeof(H2_PSEUDO_METHOD) - 1;
nva[0].value = (unsigned char *)hdbuf;
nva[0].valuelen = (size_t)(end - hdbuf);
nva[0].flags = NGHTTP2_NV_FLAG_NONE;
if(HEADER_OVERFLOW(nva[0])) {
failf(data, "Failed sending HTTP request: Header overflow");
goto fail;
}
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 = (unsigned char *)H2_PSEUDO_PATH;
nva[1].namelen = sizeof(H2_PSEUDO_PATH) - 1;
nva[1].value = (unsigned char *)hdbuf;
nva[1].valuelen = (size_t)(end - hdbuf);
nva[1].flags = NGHTTP2_NV_FLAG_NONE;
if(HEADER_OVERFLOW(nva[1])) {
failf(data, "Failed sending HTTP request: Header overflow");
goto fail;
}
nva[2].name = (unsigned char *) H2_PSEUDO_SCHEME;
nva[2].namelen = sizeof(H2_PSEUDO_SCHEME) - 1;
vptr = Curl_checkheaders(data, H2_PSEUDO_SCHEME);
if(vptr) {
vptr += sizeof(H2_PSEUDO_SCHEME);
while(*vptr && ISSPACE(*vptr))
vptr++;
nva[2].value = (unsigned char *)vptr;
infof(data, "set pseduo header %s to %s", H2_PSEUDO_SCHEME, vptr);
}
else { else {
if(conn->handler->flags & PROTOPT_SSL) unsigned int i;
nva[2].value = (unsigned char *)"https"; for(i = 0; i < nheader; i++) {
else nva[i].name = (unsigned char *)hreq->header[i].name;
nva[2].value = (unsigned char *)"http"; nva[i].namelen = hreq->header[i].namelen;
} nva[i].value = (unsigned char *)hreq->header[i].value;
nva[2].valuelen = strlen((char *)nva[2].value); nva[i].valuelen = hreq->header[i].valuelen;
nva[2].flags = NGHTTP2_NV_FLAG_NONE;
if(HEADER_OVERFLOW(nva[2])) {
failf(data, "Failed sending HTTP request: Header overflow");
goto fail;
}
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 = (unsigned char *)H2_PSEUDO_AUTHORITY;
nva[i].namelen = sizeof(H2_PSEUDO_AUTHORITY) - 1;
}
else {
nva[i].namelen = (size_t)(end - hdbuf);
/* Lower case the header name for HTTP/2 */
Curl_strntolower((char *)hdbuf, hdbuf, nva[i].namelen);
nva[i].name = (unsigned char *)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 = (uint8_t*)"trailers";
nva[i].valuelen = sizeof("trailers") - 1;
break;
default:
nva[i].value = (unsigned char *)hdbuf;
nva[i].valuelen = (size_t)(end - hdbuf);
}
nva[i].flags = NGHTTP2_NV_FLAG_NONE;
if(HEADER_OVERFLOW(nva[i])) {
failf(data, "Failed sending HTTP request: Header overflow");
goto fail;
}
++i;
}
/* :authority must come before non-pseudo header fields */
if(authority_idx && authority_idx != AUTHORITY_DST_IDX) {
nghttp2_nv 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.
It appears nghttp2 will not send a header frame larger than 64KB. */
#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;
H2BUGF(infof(data, "h2 header: %.*s:%.*s",
nva[i].namelen, nva[i].name,
nva[i].valuelen, nva[i].value));
}
if(acc > MAX_ACC) {
infof(data, "http2_send: Warning: The cumulative length of all "
"headers exceeds %d bytes and that could cause the "
"stream to be rejected.", MAX_ACC);
} }
Curl_pseudo_free(hreq);
} }
h2_pri_spec(data, &pri_spec); h2_pri_spec(data, &pri_spec);
@ -2231,11 +1992,6 @@ static ssize_t http2_send(struct Curl_easy *data, int sockindex,
nghttp2_session_resume_data(h2, stream->stream_id); nghttp2_session_resume_data(h2, stream->stream_id);
return len; return len;
fail:
free(nva);
*err = CURLE_SEND_ERROR;
return -1;
} }
CURLcode Curl_http2_setup(struct Curl_easy *data, CURLcode Curl_http2_setup(struct Curl_easy *data,