| /*************************************************************************** |
| * _ _ ____ _ |
| * Project ___| | | | _ \| | |
| * / __| | | | |_) | | |
| * | (__| |_| | _ <| |___ |
| * \___|\___/|_| \_\_____| |
| * |
| * Copyright (C) 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. |
| * |
| * SPDX-License-Identifier: curl |
| * |
| ***************************************************************************/ |
| |
| #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 an HTTP/2 or HTTP/3 request. |
| */ |
| |
| #if defined(USE_NGHTTP2) || defined(ENABLE_QUIC) |
| |
| /* 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 request */ |
| const size_t len /* size of request */, |
| size_t* hdrlen /* opt size of headers read */, |
| 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, STRCONST(H2H3_PSEUDO_SCHEME)); |
| if(vptr) { |
| vptr += sizeof(H2H3_PSEUDO_SCHEME); |
| while(*vptr && ISBLANK(*vptr)) |
| vptr++; |
| nva[2].value = vptr; |
| infof(data, "set pseudo 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); |
| } |
| |
| ++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]", |
| (int)nva[i].namelen, nva[i].name, |
| (int)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); |
| } |
| } |
| |
| if(hdrlen) { |
| /* Skip trailing CRLF */ |
| end += 4; |
| *hdrlen = end - mem; |
| } |
| |
| 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 */ |