310 lines
		
	
	
		
			8.7 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			310 lines
		
	
	
		
			8.7 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/***************************************************************************
 | 
						|
 *                                  _   _ ____  _
 | 
						|
 *  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.
 | 
						|
 *
 | 
						|
 * 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 a 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 */,
 | 
						|
                             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);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  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 */
 |