header api: add curl_easy_header and curl_easy_nextheader

Add test 1940 to 1946 to verify.

Closes #8593
This commit is contained in:
Daniel Stenberg 2022-03-17 10:20:19 +01:00
parent bdc664a640
commit d1e4a67734
No known key found for this signature in database
GPG key ID: 5CC908FDB71E12C2
38 changed files with 1798 additions and 306 deletions

View file

@ -141,6 +141,7 @@ LIB_CFILES = \
gopher.c \
h2h3.c \
hash.c \
headers.c \
hmac.c \
hostasyn.c \
hostip.c \
@ -270,6 +271,7 @@ LIB_HFILES = \
gopher.h \
h2h3.h \
hash.h \
headers.h \
hostip.h \
hsts.h \
http.h \

319
lib/headers.c Normal file
View file

@ -0,0 +1,319 @@
/***************************************************************************
* _ _ ____ _
* Project ___| | | | _ \| |
* / __| | | | |_) | |
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
* Copyright (C) 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 "strdup.h"
#include "strcase.h"
#include "headers.h"
/* The last 3 #include files should be in this order */
#include "curl_printf.h"
#include "curl_memory.h"
#include "memdebug.h"
#ifndef CURL_DISABLE_HTTP
/* Generate the curl_header struct for the user. This function MUST assign all
struct fields in the output struct. */
static void copy_header_external(struct Curl_easy *data,
struct Curl_header_store *hs,
size_t index,
size_t amount,
struct Curl_llist_element *e,
struct curl_header **hout)
{
struct curl_header *h = *hout = &data->state.headerout;
h->name = hs->name;
h->value = hs->value;
h->amount = amount;
h->index = index;
/* this will randomly OR a reverved bit for the sole purpose of making it
impossible for applications to do == comparisons, as that would
otherwise be very tempting and then lead the reserved bits not being
reserved anymore. */
h->origin = hs->type | (1<<27);
h->anchor = e;
}
/* public API */
CURLHcode curl_easy_header(CURL *easy,
const char *name,
size_t nameindex,
unsigned int type,
int request,
struct curl_header **hout)
{
struct Curl_llist_element *e;
struct Curl_llist_element *e_pick = NULL;
struct Curl_easy *data = easy;
size_t match = 0;
size_t amount = 0;
struct Curl_header_store *hs = NULL;
struct Curl_header_store *pick = NULL;
if(!name || !hout || !data ||
(type > (CURLH_HEADER|CURLH_TRAILER|CURLH_CONNECT|CURLH_1XX)) ||
!type || (request < -1))
return CURLHE_BAD_ARGUMENT;
if(!Curl_llist_count(&data->state.httphdrs))
return CURLHE_NOHEADERS; /* no headers available */
if(request > data->state.requests)
return CURLHE_NOREQUEST;
if(request == -1)
request = data->state.requests;
/* we need a first round to count amount of this header */
for(e = data->state.httphdrs.head; e; e = e->next) {
hs = e->ptr;
if(strcasecompare(hs->name, name) &&
(hs->type & type) &&
(hs->request == request)) {
amount++;
pick = hs;
e_pick = e;
}
}
if(!amount)
return CURLHE_MISSING;
else if(nameindex >= amount)
return CURLHE_BADINDEX;
if(nameindex == amount - 1)
/* if the last or only ocurrance is what's asked for, then we know it */
hs = pick;
else {
for(e = data->state.httphdrs.head; e; e = e->next) {
hs = e->ptr;
if(strcasecompare(hs->name, name) &&
(hs->type & type) &&
(hs->request == request) &&
(match++ == nameindex)) {
e_pick = e;
break;
}
}
if(!e) /* this shouldn't happen */
return CURLHE_MISSING;
}
/* this is the name we want */
copy_header_external(data, hs, nameindex, amount, e_pick, hout);
return CURLHE_OK;
}
/* public API */
struct curl_header *curl_easy_nextheader(CURL *easy,
unsigned int type,
int request,
struct curl_header *prev)
{
struct Curl_easy *data = easy;
struct Curl_llist_element *pick;
struct Curl_llist_element *e;
struct Curl_header_store *hs;
struct curl_header *hout;
size_t amount = 0;
size_t index = 0;
if(request > data->state.requests)
return NULL;
if(request == -1)
request = data->state.requests;
if(prev) {
pick = prev->anchor;
if(!pick)
/* something is wrong */
return NULL;
pick = pick->next;
}
else
pick = data->state.httphdrs.head;
if(pick) {
/* make sure it is the next header of the desired type */
do {
hs = pick->ptr;
if((hs->type & type) && (hs->request == request))
break;
} while((pick = pick->next));
}
if(!pick)
/* no more headers available */
return NULL;
hs = pick->ptr;
/* count number of occurrences of this name within the mask and figure out
the index for the currently selected entry */
for(e = data->state.httphdrs.head; e; e = e->next) {
struct Curl_header_store *check = e->ptr;
if(strcasecompare(hs->name, check->name) &&
(check->request == request) &&
(check->type & type))
amount++;
if(e == pick)
index = amount - 1;
}
copy_header_external(data, hs, index, amount, pick, &hout);
return hout;
}
static CURLcode namevalue(char *header, size_t hlen, unsigned int type,
char **name, char **value)
{
char *end = header + hlen - 1; /* point to the last byte */
DEBUGASSERT(hlen);
*name = header;
if(type == CURLH_PSEUDO) {
if(*header != ':')
return CURLE_BAD_FUNCTION_ARGUMENT;
header++;
}
/* Find the end of the header name */
while(*header && (*header != ':'))
++header;
if(*header)
/* Skip over colon, null it */
*header++ = 0;
else
return CURLE_BAD_FUNCTION_ARGUMENT;
/* skip all leading space letters */
while(*header && ISSPACE(*header))
header++;
*value = header;
/* skip all trailing space letters */
while((end > header) && ISSPACE(*end))
*end-- = 0; /* nul terminate */
return CURLE_OK;
}
/*
* Curl_headers_push() gets passed a full HTTP header to store. It gets called
* immediately before the header callback. The header is CRLF terminated.
*/
CURLcode Curl_headers_push(struct Curl_easy *data, const char *header,
unsigned char type)
{
char *value = NULL;
char *name = NULL;
char *end;
size_t hlen; /* length of the incoming header */
struct Curl_header_store *hs;
CURLcode result = CURLE_OUT_OF_MEMORY;
if((header[0] == '\r') || (header[0] == '\n'))
/* ignore the body separator */
return CURLE_OK;
end = strchr(header, '\r');
if(!end) {
end = strchr(header, '\n');
if(!end)
return CURLE_BAD_FUNCTION_ARGUMENT;
}
hlen = end - header + 1;
hs = calloc(1, sizeof(*hs) + hlen);
if(!hs)
return CURLE_OUT_OF_MEMORY;
memcpy(hs->buffer, header, hlen);
hs->buffer[hlen] = 0; /* nul terminate */
result = namevalue(hs->buffer, hlen, type, &name, &value);
if(result)
goto fail;
hs->name = name;
hs->value = value;
hs->type = type;
hs->request = data->state.requests;
/* insert this node into the list of headers */
Curl_llist_insert_next(&data->state.httphdrs, data->state.httphdrs.tail,
hs, &hs->node);
return CURLE_OK;
fail:
free(hs);
return result;
}
/*
* Curl_headers_init(). Init the headers subsystem.
*/
static void headers_init(struct Curl_easy *data)
{
Curl_llist_init(&data->state.httphdrs, NULL);
}
/*
* Curl_headers_cleanup(). Free all stored headers and associated memory.
*/
CURLcode Curl_headers_cleanup(struct Curl_easy *data)
{
struct Curl_llist_element *e;
struct Curl_llist_element *n;
for(e = data->state.httphdrs.head; e; e = n) {
struct Curl_header_store *hs = e->ptr;
n = e->next;
free(hs);
}
headers_init(data);
return CURLE_OK;
}
#else /* HTTP-disabled builds below */
CURLHcode curl_easy_header(CURL *easy,
const char *name,
size_t index,
unsigned int type,
struct curl_header **hout)
{
(void)easy;
(void)name;
(void)index;
(void)type;
(void)hout;
return CURLHE_NOT_BUILT_IN;
}
struct curl_header *curl_easy_nextheader(CURL *easy,
unsigned int type,
struct curl_header *prev)
{
(void)easy;
(void)type;
(void)prev;
return NULL;
}
#endif

53
lib/headers.h Normal file
View file

@ -0,0 +1,53 @@
#ifndef HEADER_CURL_HEADER_H
#define HEADER_CURL_HEADER_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"
#ifndef CURL_DISABLE_HTTP
struct Curl_header_store {
struct Curl_llist_element node;
char *name; /* points into 'buffer' */
char *value; /* points into 'buffer */
int request; /* 0 is the first request, then 1.. 2.. */
unsigned char type; /* CURLH_* defines */
char buffer[1]; /* this is the raw header blob */
};
/*
* Curl_headers_push() gets passed a full header to store.
*/
CURLcode Curl_headers_push(struct Curl_easy *data, const char *header,
unsigned char type);
/*
* Curl_headers_cleanup(). Free all stored headers and associated memory.
*/
CURLcode Curl_headers_cleanup(struct Curl_easy *data);
#else
#define Curl_headers_push(x,y,z) CURLE_NOT_BUILT_IN
#define Curl_headers_cleanup(x) Curl_nop_stmt
#endif
#endif /* HEADER_CURL_HEADER_H */

View file

@ -4020,9 +4020,9 @@ CURLcode Curl_http_readwrite_headers(struct Curl_easy *data,
/* now, only output this if the header AND body are requested:
*/
writetype = CLIENTWRITE_HEADER;
if(data->set.include_header)
writetype |= CLIENTWRITE_BODY;
writetype = CLIENTWRITE_HEADER |
(data->set.include_header ? CLIENTWRITE_BODY : 0) |
((k->httpcode/100 == 1) ? CLIENTWRITE_1XX : 0);
headerlen = Curl_dyn_len(&data->state.headerb);
result = Curl_client_write(data, writetype,
@ -4175,6 +4175,7 @@ CURLcode Curl_http_readwrite_headers(struct Curl_easy *data,
* Checks for special headers coming up.
*/
writetype = CLIENTWRITE_HEADER;
if(!k->headerline++) {
/* This is the first header, it MUST be the error code line
or else we consider this to be the body right away! */
@ -4299,6 +4300,7 @@ CURLcode Curl_http_readwrite_headers(struct Curl_easy *data,
result = Curl_http_statusline(data, conn);
if(result)
return result;
writetype |= CLIENTWRITE_STATUS;
}
else {
k->header = FALSE; /* this is not a header line */
@ -4317,10 +4319,10 @@ CURLcode Curl_http_readwrite_headers(struct Curl_easy *data,
/*
* End of header-checks. Write them to the client.
*/
writetype = CLIENTWRITE_HEADER;
if(data->set.include_header)
writetype |= CLIENTWRITE_BODY;
if(k->httpcode/100 == 1)
writetype |= CLIENTWRITE_1XX;
Curl_debug(data, CURLINFO_HEADER_IN, headp,
Curl_dyn_len(&data->state.headerb));

View file

@ -39,6 +39,7 @@
#include "transfer.h"
#include "dynbuf.h"
#include "h2h3.h"
#include "headers.h"
/* The last 3 #include files should be in this order */
#include "curl_printf.h"
#include "curl_memory.h"
@ -1078,9 +1079,14 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame,
/* nghttp2 guarantees :status is received first and only once, and
value is 3 digits status code, and decode_status_code always
succeeds. */
char buffer[32];
stream->status_code = decode_status_code(value, valuelen);
DEBUGASSERT(stream->status_code != -1);
msnprintf(buffer, sizeof(buffer), H2H3_PSEUDO_STATUS ":%u\r",
stream->status_code);
result = Curl_headers_push(data_s, buffer, CURLH_PSEUDO);
if(result)
return NGHTTP2_ERR_CALLBACK_FAILURE;
result = Curl_dyn_addn(&stream->header_recvbuf, STRCONST("HTTP/2 "));
if(result)
return NGHTTP2_ERR_CALLBACK_FAILURE;

View file

@ -221,7 +221,9 @@ CHUNKcode Curl_httpchunk_read(struct Curl_easy *data,
tr = Curl_dyn_ptr(&conn->trailer);
trlen = Curl_dyn_len(&conn->trailer);
if(!data->set.http_te_skip) {
result = Curl_client_write(data, CLIENTWRITE_HEADER, tr, trlen);
result = Curl_client_write(data,
CLIENTWRITE_HEADER|CLIENTWRITE_TRAILER,
tr, trlen);
if(result) {
*extrap = result;
return CHUNKE_PASSTHRU_ERROR;

View file

@ -345,6 +345,7 @@ static CURLcode CONNECT(struct Curl_easy *data,
/* Send the connect request to the proxy */
result = Curl_buffer_send(req, data, &data->info.request_size, 0,
sockindex);
s->headerlines = 0;
}
if(result)
failf(data, "Failed sending CONNECT to proxy");
@ -480,6 +481,7 @@ static CURLcode CONNECT(struct Curl_easy *data,
if(byte != 0x0a)
continue;
s->headerlines++;
linep = Curl_dyn_ptr(&s->rcvbuf);
perline = Curl_dyn_len(&s->rcvbuf); /* amount of bytes in this line */
@ -488,9 +490,9 @@ static CURLcode CONNECT(struct Curl_easy *data,
if(!data->set.suppress_connect_headers) {
/* send the header to the callback */
int writetype = CLIENTWRITE_HEADER;
if(data->set.include_header)
writetype |= CLIENTWRITE_BODY;
int writetype = CLIENTWRITE_HEADER | CLIENTWRITE_CONNECT |
(data->set.include_header ? CLIENTWRITE_BODY : 0) |
(s->headerlines == 1 ? CLIENTWRITE_STATUS : 0);
result = Curl_client_write(data, writetype, linep, perline);
if(result)

View file

@ -7,7 +7,7 @@
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
* Copyright (C) 1998 - 2021, Daniel Stenberg, <daniel@haxx.se>, et al.
* 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
@ -59,6 +59,7 @@ struct http_connect_state {
struct dynbuf rcvbuf;
struct dynbuf req;
size_t nsend;
size_t headerlines;
enum keeponval {
KEEPON_DONE,
KEEPON_CONNECT,

View file

@ -719,7 +719,6 @@ static CURLcode multi_done(struct Curl_easy *data,
}
Curl_safefree(data->state.buffer);
Curl_free_request_state(data);
return result;
}

View file

@ -45,6 +45,7 @@
#include "select.h"
#include "strdup.h"
#include "http2.h"
#include "headers.h"
/* The last 3 #include files should be in this order */
#include "curl_printf.h"
@ -580,21 +581,33 @@ static CURLcode chop_write(struct Curl_easy *data,
len -= chunklen;
}
/* HTTP header, but not status-line */
if((conn->handler->protocol & PROTO_FAMILY_HTTP) &&
(type & CLIENTWRITE_HEADER) && !(type & CLIENTWRITE_STATUS) ) {
CURLcode result =
Curl_headers_push(data, optr,
type & CLIENTWRITE_CONNECT ? CURLH_CONNECT :
(type & CLIENTWRITE_1XX ? CURLH_1XX :
(type & CLIENTWRITE_TRAILER ? CURLH_TRAILER :
CURLH_HEADER)));
if(result)
return result;
}
if(writeheader) {
size_t wrote;
ptr = optr;
len = olen;
Curl_set_in_callback(data, true);
wrote = writeheader(ptr, 1, len, data->set.writeheader);
wrote = writeheader(optr, 1, olen, data->set.writeheader);
Curl_set_in_callback(data, false);
if(CURL_WRITEFUNC_PAUSE == wrote)
/* here we pass in the HEADER bit only since if this was body as well
then it was passed already and clearly that didn't trigger the
pause, so this is saved for later with the HEADER bit only */
return pausewrite(data, CLIENTWRITE_HEADER, ptr, len);
return pausewrite(data, CLIENTWRITE_HEADER, optr, olen);
if(wrote != len) {
if(wrote != olen) {
failf(data, "Failed writing header");
return CURLE_WRITE_ERROR;
}
@ -620,8 +633,6 @@ CURLcode Curl_client_write(struct Curl_easy *data,
{
struct connectdata *conn = data->conn;
DEBUGASSERT(!(type & ~CLIENTWRITE_BOTH));
if(!len)
return CURLE_OK;

View file

@ -7,7 +7,7 @@
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
* Copyright (C) 1998 - 2021, Daniel Stenberg, <daniel@haxx.se>, et al.
* 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
@ -45,8 +45,12 @@ void Curl_failf(struct Curl_easy *, const char *fmt, ...);
#define failf Curl_failf
#define CLIENTWRITE_BODY (1<<0)
#define CLIENTWRITE_HEADER (1<<1)
#define CLIENTWRITE_BODY (1<<0)
#define CLIENTWRITE_HEADER (1<<1)
#define CLIENTWRITE_STATUS (1<<2) /* the first "header" is the status line */
#define CLIENTWRITE_CONNECT (1<<3) /* a CONNECT response */
#define CLIENTWRITE_1XX (1<<4) /* a 1xx response */
#define CLIENTWRITE_TRAILER (1<<5) /* a trailer header */
#define CLIENTWRITE_BOTH (CLIENTWRITE_BODY|CLIENTWRITE_HEADER)
CURLcode Curl_client_write(struct Curl_easy *data, int type, char *ptr,

View file

@ -79,6 +79,7 @@
#include "urlapi-int.h"
#include "hsts.h"
#include "setopt.h"
#include "headers.h"
/* The last 3 #include files should be in this order */
#include "curl_printf.h"
@ -1489,6 +1490,7 @@ CURLcode Curl_pretransfer(struct Curl_easy *data)
data->set.str[STRING_PROXYPASSWORD]);
data->req.headerbytecount = 0;
Curl_headers_cleanup(data);
return result;
}
@ -1533,6 +1535,8 @@ CURLcode Curl_follow(struct Curl_easy *data,
DEBUGASSERT(type != FOLLOW_NONE);
if(type != FOLLOW_FAKE)
data->state.requests++; /* count all real follows */
if(type == FOLLOW_REDIR) {
if((data->set.maxredirs != -1) &&
(data->state.followlocation >= data->set.maxredirs)) {

View file

@ -130,6 +130,7 @@ bool curl_win32_idn_to_ascii(const char *in, char **out);
#include "setopt.h"
#include "altsvc.h"
#include "dynbuf.h"
#include "headers.h"
/* The last 3 #include files should be in this order */
#include "curl_printf.h"
@ -470,6 +471,7 @@ CURLcode Curl_close(struct Curl_easy **datap)
/* destruct wildcard structures if it is needed */
Curl_wildcard_dtor(&data->wildcard);
Curl_freeset(data);
Curl_headers_cleanup(data);
free(data);
return CURLE_OK;
}

View file

@ -1343,6 +1343,7 @@ struct UrlState {
int os_errno; /* filled in with errno whenever an error occurs */
char *scratch; /* huge buffer[set.buffer_size*2] for upload CRLF replacing */
long followlocation; /* redirect counter */
int requests; /* request counter: redirects + authentication retakes */
#ifdef HAVE_SIGNAL
/* storage for the previous bag^H^H^HSIGPIPE signal handler :-) */
void (*prev_signal)(int sig);
@ -1414,6 +1415,8 @@ struct UrlState {
size_t trailers_bytes_sent;
struct dynbuf trailers_buf; /* a buffer containing the compiled trailing
headers */
struct Curl_llist httphdrs; /* received headers */
struct curl_header headerout; /* for external purposes */
#endif
trailers_state trailers_state; /* whether we are sending trailers
and what stage are we at */