Skip to content

Commit

Permalink
http2: support HTTP/2 to forward proxies, non-tunneling
Browse files Browse the repository at this point in the history
- with `--proxy-http2` allow h2 ALPN negotiation to
  forward proxies
- applies to http: requests against a https: proxy only,
  as https: requests will auto-tunnel
- adding a HTTP/1 request parser in http1.c
- removed h2h3.c
- using new request parser in nghttp2 and all h3 backends
- adding test 2603 for request parser
- adding h2 proxy test cases to test_10_*

scorecard.py: request scoring accidentally always run curl
with '-v'. Removed that, expect double numbers.

labeller: added http1.* and h2-proxy sources to detection

Closes #10967
  • Loading branch information
icing authored and bagder committed Apr 17, 2023
1 parent fb1d62f commit fc2f1e5
Show file tree
Hide file tree
Showing 28 changed files with 1,521 additions and 823 deletions.
3 changes: 2 additions & 1 deletion .github/labeler.yml
Expand Up @@ -156,8 +156,9 @@ HTTP:
- all: ['docs/libcurl/opts/CURLOPT_TRAILER*']
- all: ['docs/libcurl/opts/CURLOPT_TRANSFER_ENCODING*']
- all: ['lib/cf-https*']
- all: ['lib/cf-h1*']
- all: ['lib/cf-h2*']
- all: ['lib/cookie.*']
- all: ['lib/h2h3.*']
- all: ['lib/http*']
- all: ['tests/http*']
- all: ['tests/http-server.pl']
Expand Down
4 changes: 2 additions & 2 deletions lib/Makefile.inc
Expand Up @@ -153,7 +153,6 @@ LIB_CFILES = \
getenv.c \
getinfo.c \
gopher.c \
h2h3.c \
hash.c \
headers.c \
hmac.c \
Expand All @@ -164,6 +163,7 @@ LIB_CFILES = \
hostsyn.c \
hsts.c \
http.c \
http1.c \
http2.c \
http_chunks.c \
http_digest.c \
Expand Down Expand Up @@ -296,12 +296,12 @@ LIB_HFILES = \
ftplistparser.h \
getinfo.h \
gopher.h \
h2h3.h \
hash.h \
headers.h \
hostip.h \
hsts.h \
http.h \
http1.h \
http2.h \
http_chunks.h \
http_digest.h \
Expand Down
22 changes: 22 additions & 0 deletions lib/bufq.c
Expand Up @@ -142,6 +142,21 @@ static size_t chunk_skip(struct buf_chunk *chunk, size_t amount)
return n;
}

static void chunk_shift(struct buf_chunk *chunk)
{
if(chunk->r_offset) {
if(!chunk_is_empty(chunk)) {
size_t n = chunk->w_offset - chunk->r_offset;
memmove(chunk->x.data, chunk->x.data + chunk->r_offset, n);
chunk->w_offset -= chunk->r_offset;
chunk->r_offset = 0;
}
else {
chunk->r_offset = chunk->w_offset = 0;
}
}
}

static void chunk_list_free(struct buf_chunk **anchor)
{
struct buf_chunk *chunk;
Expand Down Expand Up @@ -479,6 +494,13 @@ void Curl_bufq_skip(struct bufq *q, size_t amount)
}
}

void Curl_bufq_skip_and_shift(struct bufq *q, size_t amount)
{
Curl_bufq_skip(q, amount);
if(q->tail)
chunk_shift(q->tail);
}

ssize_t Curl_bufq_pass(struct bufq *q, Curl_bufq_writer *writer,
void *writer_ctx, CURLcode *err)
{
Expand Down
6 changes: 6 additions & 0 deletions lib/bufq.h
Expand Up @@ -214,6 +214,12 @@ bool Curl_bufq_peek_at(struct bufq *q, size_t offset,
*/
void Curl_bufq_skip(struct bufq *q, size_t amount);

/**
* Same as `skip` but shift tail data to the start afterwards,
* so that further writes will find room in tail.
*/
void Curl_bufq_skip_and_shift(struct bufq *q, size_t amount);

typedef ssize_t Curl_bufq_writer(void *writer_ctx,
const unsigned char *buf, size_t len,
CURLcode *err);
Expand Down
69 changes: 20 additions & 49 deletions lib/cf-h2-proxy.c
Expand Up @@ -34,7 +34,7 @@
#include "bufq.h"
#include "dynbuf.h"
#include "dynhds.h"
#include "h2h3.h"
#include "http1.h"
#include "http_proxy.h"
#include "multiif.h"
#include "cf-h2-proxy.h"
Expand Down Expand Up @@ -648,8 +648,8 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame,
return 0;
}

if(namelen == sizeof(H2H3_PSEUDO_STATUS) - 1 &&
memcmp(H2H3_PSEUDO_STATUS, name, namelen) == 0) {
if(namelen == sizeof(HTTP_PSEUDO_STATUS) - 1 &&
memcmp(HTTP_PSEUDO_STATUS, name, namelen) == 0) {
int http_status;
struct http_resp *resp;

Expand Down Expand Up @@ -783,60 +783,28 @@ static CURLcode h2_submit(int32_t *pstream_id,
nghttp2_data_source_read_callback read_callback,
void *read_ctx)
{
struct dynhds h2_headers;
nghttp2_nv *nva = NULL;
unsigned int i;
int32_t stream_id = -1;
size_t nheader, j;
CURLcode result = CURLE_OUT_OF_MEMORY;
size_t nheader;
CURLcode result;

(void)cf;
nheader = req->headers.hds_len + 1; /* ":method" is a MUST */
if(req->scheme)
++nheader;
if(req->authority)
++nheader;
if(req->path)
++nheader;
Curl_dynhds_init(&h2_headers, 0, DYN_HTTP_REQUEST);
result = Curl_http_req_to_h2(&h2_headers, req, data);
if(result)
goto out;

nheader = Curl_dynhds_count(&h2_headers);
nva = malloc(sizeof(nghttp2_nv) * nheader);
if(!nva)
if(!nva) {
result = CURLE_OUT_OF_MEMORY;
goto out;

nva[0].name = (unsigned char *)H2H3_PSEUDO_METHOD;
nva[0].namelen = sizeof(H2H3_PSEUDO_METHOD) - 1;
nva[0].value = (unsigned char *)req->method;
nva[0].valuelen = strlen(req->method);
nva[0].flags = NGHTTP2_NV_FLAG_NONE;
i = 1;
if(req->scheme) {
nva[i].name = (unsigned char *)H2H3_PSEUDO_SCHEME;
nva[i].namelen = sizeof(H2H3_PSEUDO_SCHEME) - 1;
nva[i].value = (unsigned char *)req->scheme;
nva[i].valuelen = strlen(req->scheme);
nva[i].flags = NGHTTP2_NV_FLAG_NONE;
++i;
}
if(req->authority) {
nva[i].name = (unsigned char *)H2H3_PSEUDO_AUTHORITY;
nva[i].namelen = sizeof(H2H3_PSEUDO_AUTHORITY) - 1;
nva[i].value = (unsigned char *)req->authority;
nva[i].valuelen = strlen(req->authority);
nva[i].flags = NGHTTP2_NV_FLAG_NONE;
++i;
}
if(req->path) {
nva[i].name = (unsigned char *)H2H3_PSEUDO_PATH;
nva[i].namelen = sizeof(H2H3_PSEUDO_PATH) - 1;
nva[i].value = (unsigned char *)req->path;
nva[i].valuelen = strlen(req->path);
nva[i].flags = NGHTTP2_NV_FLAG_NONE;
++i;
}

for(j = 0; i < nheader; i++, j++) {
struct dynhds_entry *e = Curl_dynhds_getn(&req->headers, j);
if(!e)
break;
for(i = 0; i < nheader; ++i) {
struct dynhds_entry *e = Curl_dynhds_getn(&h2_headers, i);
nva[i].name = (unsigned char *)e->name;
nva[i].namelen = e->namelen;
nva[i].value = (unsigned char *)e->value;
Expand Down Expand Up @@ -866,7 +834,8 @@ static CURLcode h2_submit(int32_t *pstream_id,
result = CURLE_OK;

out:
Curl_safefree(nva);
free(nva);
Curl_dynhds_free(&h2_headers);
*pstream_id = stream_id;
return result;
}
Expand All @@ -881,7 +850,9 @@ static CURLcode submit_CONNECT(struct Curl_cfilter *cf,

infof(data, "Establish HTTP/2 proxy tunnel to %s", ts->authority);

result = Curl_http_req_make(&req, "CONNECT", NULL, ts->authority, NULL);
result = Curl_http_req_make(&req, "CONNECT", sizeof("CONNECT")-1,
NULL, 0, ts->authority, strlen(ts->authority),
NULL, 0);
if(result)
goto out;

Expand Down
102 changes: 83 additions & 19 deletions lib/dynhds.c
Expand Up @@ -34,7 +34,7 @@

static struct dynhds_entry *
entry_new(const char *name, size_t namelen,
const char *value, size_t valuelen)
const char *value, size_t valuelen, int opts)
{
struct dynhds_entry *e;
char *p;
Expand All @@ -50,9 +50,35 @@ entry_new(const char *name, size_t namelen,
e->value = p += namelen + 1; /* leave a \0 at the end of name */
memcpy(p, value, valuelen);
e->valuelen = valuelen;
if(opts & DYNHDS_OPT_LOWERCASE)
Curl_strntolower(e->name, e->name, e->namelen);
return e;
}

static struct dynhds_entry *
entry_append(struct dynhds_entry *e,
const char *value, size_t valuelen)
{
struct dynhds_entry *e2;
size_t valuelen2 = e->valuelen + 1 + valuelen;
char *p;

DEBUGASSERT(value);
e2 = calloc(1, sizeof(*e) + e->namelen + valuelen2 + 2);
if(!e2)
return NULL;
e2->name = p = ((char *)e2) + sizeof(*e2);
memcpy(p, e->name, e->namelen);
e2->namelen = e->namelen;
e2->value = p += e->namelen + 1; /* leave a \0 at the end of name */
memcpy(p, e->value, e->valuelen);
p += e->valuelen;
p[0] = ' ';
memcpy(p + 1, value, valuelen);
e2->valuelen = valuelen2;
return e2;
}

static void entry_free(struct dynhds_entry *e)
{
free(e);
Expand All @@ -67,6 +93,7 @@ void Curl_dynhds_init(struct dynhds *dynhds, size_t max_entries,
dynhds->hds_len = dynhds->hds_allc = dynhds->strs_len = 0;
dynhds->max_entries = max_entries;
dynhds->max_strs_size = max_strs_size;
dynhds->opts = 0;
}

void Curl_dynhds_free(struct dynhds *dynhds)
Expand Down Expand Up @@ -102,6 +129,11 @@ size_t Curl_dynhds_count(struct dynhds *dynhds)
return dynhds->hds_len;
}

void Curl_dynhds_set_opts(struct dynhds *dynhds, int opts)
{
dynhds->opts = opts;
}

struct dynhds_entry *Curl_dynhds_getn(struct dynhds *dynhds, size_t n)
{
DEBUGASSERT(dynhds);
Expand Down Expand Up @@ -150,7 +182,7 @@ CURLcode Curl_dynhds_add(struct dynhds *dynhds,
if(dynhds->strs_len + namelen + valuelen > dynhds->max_strs_size)
return CURLE_OUT_OF_MEMORY;

entry = entry_new(name, namelen, value, valuelen);
entry = entry_new(name, namelen, value, valuelen, dynhds->opts);
if(!entry)
goto out;

Expand Down Expand Up @@ -203,33 +235,65 @@ CURLcode Curl_dynhds_cset(struct dynhds *dynhds,
return Curl_dynhds_set(dynhds, name, strlen(name), value, strlen(value));
}

CURLcode Curl_dynhds_h1_cadd_line(struct dynhds *dynhds, const char *line)
CURLcode Curl_dynhds_h1_add_line(struct dynhds *dynhds,
const char *line, size_t line_len)
{
const char *p;
const char *name;
size_t namelen;
const char *value;
size_t valuelen;
size_t valuelen, i;

if(!line)
if(!line || !line_len)
return CURLE_OK;

if((line[0] == ' ') || (line[0] == '\t')) {
struct dynhds_entry *e, *e2;
/* header continuation, yikes! */
if(!dynhds->hds_len)
return CURLE_BAD_FUNCTION_ARGUMENT;

while(line_len && ISBLANK(line[0])) {
++line;
--line_len;
}
if(!line_len)
return CURLE_BAD_FUNCTION_ARGUMENT;
e = dynhds->hds[dynhds->hds_len-1];
e2 = entry_append(e, line, line_len);
if(!e2)
return CURLE_OUT_OF_MEMORY;
dynhds->hds[dynhds->hds_len-1] = e2;
entry_free(e);
return CURLE_OK;
p = strchr(line, ':');
if(!p) {
return CURLE_BAD_FUNCTION_ARGUMENT;
}
else {
p = memchr(line, ':', line_len);
if(!p)
return CURLE_BAD_FUNCTION_ARGUMENT;
name = line;
namelen = p - line;
p++; /* move past the colon */
for(i = namelen + 1; i < line_len; ++i, ++p) {
if(!ISBLANK(*p))
break;
}
value = p;
valuelen = line_len - i;

name = line;
namelen = p - line;
p++; /* move past the colon */
while(ISBLANK(*p))
p++;
value = p;
p = strchr(value, '\r');
if(!p)
p = strchr(value, '\n');
valuelen = p? ((size_t)(p - value)) : strlen(value);
p = memchr(value, '\r', valuelen);
if(!p)
p = memchr(value, '\n', valuelen);
if(p)
valuelen = (size_t)(p - value);

return Curl_dynhds_add(dynhds, name, namelen, value, valuelen);
return Curl_dynhds_add(dynhds, name, namelen, value, valuelen);
}
}

CURLcode Curl_dynhds_h1_cadd_line(struct dynhds *dynhds, const char *line)
{
return Curl_dynhds_h1_add_line(dynhds, line, line? strlen(line) : 0);
}

size_t Curl_dynhds_count_name(struct dynhds *dynhds,
Expand Down

0 comments on commit fc2f1e5

Please sign in to comment.