| /*************************************************************************** |
| * _ _ ____ _ |
| * 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 "strdup.h" |
| #include "strcase.h" |
| #include "sendf.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" |
| |
| #if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_HEADERS_API) |
| |
| /* 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_header_store *hs, |
| size_t index, |
| size_t amount, |
| struct Curl_llist_element *e, |
| struct curl_header *hout) |
| { |
| struct curl_header *h = hout; |
| h->name = hs->name; |
| h->value = hs->value; |
| h->amount = amount; |
| h->index = index; |
| /* this will randomly OR a reserved bit for the sole purpose of making it |
| impossible for applications to do == comparisons, as that would otherwise |
| be very tempting and then lead to 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| |
| CURLH_PSEUDO)) || !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 occurrence 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(hs, nameindex, amount, e_pick, |
| &data->state.headerout[0]); |
| *hout = &data->state.headerout[0]; |
| 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; |
| 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; |
| pick = pick->next; |
| } while(pick); |
| } |
| |
| 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(hs, index, amount, pick, |
| &data->state.headerout[1]); |
| return &data->state.headerout[1]; |
| } |
| |
| 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 && ISBLANK(*header)) |
| header++; |
| |
| *value = header; |
| |
| /* skip all trailing space letters */ |
| while((end > header) && ISSPACE(*end)) |
| *end-- = 0; /* nul terminate */ |
| return CURLE_OK; |
| } |
| |
| static CURLcode unfold_value(struct Curl_easy *data, const char *value, |
| size_t vlen) /* length of the incoming header */ |
| { |
| struct Curl_header_store *hs; |
| struct Curl_header_store *newhs; |
| size_t olen; /* length of the old value */ |
| size_t oalloc; /* length of the old name + value + separator */ |
| size_t offset; |
| DEBUGASSERT(data->state.prevhead); |
| hs = data->state.prevhead; |
| olen = strlen(hs->value); |
| offset = hs->value - hs->buffer; |
| oalloc = olen + offset + 1; |
| |
| /* skip all trailing space letters */ |
| while(vlen && ISSPACE(value[vlen - 1])) |
| vlen--; |
| |
| /* save only one leading space */ |
| while((vlen > 1) && ISBLANK(value[0]) && ISBLANK(value[1])) { |
| vlen--; |
| value++; |
| } |
| |
| /* since this header block might move in the realloc below, it needs to |
| first be unlinked from the list and then re-added again after the |
| realloc */ |
| Curl_llist_remove(&data->state.httphdrs, &hs->node, NULL); |
| |
| /* new size = struct + new value length + old name+value length */ |
| newhs = Curl_saferealloc(hs, sizeof(*hs) + vlen + oalloc + 1); |
| if(!newhs) |
| return CURLE_OUT_OF_MEMORY; |
| /* ->name' and ->value point into ->buffer (to keep the header allocation |
| in a single memory block), which now potentially have moved. Adjust |
| them. */ |
| newhs->name = newhs->buffer; |
| newhs->value = &newhs->buffer[offset]; |
| |
| /* put the data at the end of the previous data, not the newline */ |
| memcpy(&newhs->value[olen], value, vlen); |
| newhs->value[olen + vlen] = 0; /* null-terminate at newline */ |
| |
| /* insert this node into the list of headers */ |
| Curl_llist_insert_next(&data->state.httphdrs, data->state.httphdrs.tail, |
| newhs, &newhs->node); |
| data->state.prevhead = newhs; |
| 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) |
| /* neither CR nor LF as terminator is not a valid header */ |
| return CURLE_WEIRD_SERVER_REPLY; |
| } |
| hlen = end - header; |
| |
| if((header[0] == ' ') || (header[0] == '\t')) { |
| if(data->state.prevhead) |
| /* line folding, append value to the previous header's value */ |
| return unfold_value(data, header, hlen); |
| else { |
| /* Can't unfold without a previous header. Instead of erroring, just |
| pass the leading blanks. */ |
| while(hlen && ISBLANK(*header)) { |
| header++; |
| hlen--; |
| } |
| if(!hlen) |
| return CURLE_WEIRD_SERVER_REPLY; |
| } |
| } |
| |
| 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) { |
| 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); |
| data->state.prevhead = hs; |
| } |
| else |
| free(hs); |
| return result; |
| } |
| |
| /* |
| * Curl_headers_reset(). Reset the headers subsystem. |
| */ |
| static void headers_reset(struct Curl_easy *data) |
| { |
| Curl_llist_init(&data->state.httphdrs, NULL); |
| data->state.prevhead = NULL; |
| } |
| |
| struct hds_cw_collect_ctx { |
| struct Curl_cwriter super; |
| }; |
| |
| static CURLcode hds_cw_collect_write(struct Curl_easy *data, |
| struct Curl_cwriter *writer, int type, |
| const char *buf, size_t blen) |
| { |
| if((type & CLIENTWRITE_HEADER) && !(type & CLIENTWRITE_STATUS)) { |
| unsigned char htype = (unsigned char) |
| (type & CLIENTWRITE_CONNECT ? CURLH_CONNECT : |
| (type & CLIENTWRITE_1XX ? CURLH_1XX : |
| (type & CLIENTWRITE_TRAILER ? CURLH_TRAILER : |
| CURLH_HEADER))); |
| CURLcode result = Curl_headers_push(data, buf, htype); |
| if(result) |
| return result; |
| } |
| return Curl_cwriter_write(data, writer->next, type, buf, blen); |
| } |
| |
| static const struct Curl_cwtype hds_cw_collect = { |
| "hds-collect", |
| NULL, |
| Curl_cwriter_def_init, |
| hds_cw_collect_write, |
| Curl_cwriter_def_close, |
| sizeof(struct hds_cw_collect_ctx) |
| }; |
| |
| CURLcode Curl_headers_init(struct Curl_easy *data) |
| { |
| struct Curl_cwriter *writer; |
| CURLcode result; |
| |
| if(data->conn && (data->conn->handler->protocol & PROTO_FAMILY_HTTP)) { |
| /* avoid installing it twice */ |
| if(Curl_cwriter_get_by_name(data, hds_cw_collect.name)) |
| return CURLE_OK; |
| |
| result = Curl_cwriter_create(&writer, data, &hds_cw_collect, |
| CURL_CW_PROTOCOL); |
| if(result) |
| return result; |
| |
| result = Curl_cwriter_add(data, writer); |
| if(result) { |
| Curl_cwriter_free(data, writer); |
| return result; |
| } |
| } |
| return CURLE_OK; |
| } |
| |
| /* |
| * 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_reset(data); |
| return CURLE_OK; |
| } |
| |
| #else /* HTTP-disabled builds below */ |
| |
| CURLHcode curl_easy_header(CURL *easy, |
| const char *name, |
| size_t index, |
| unsigned int origin, |
| int request, |
| struct curl_header **hout) |
| { |
| (void)easy; |
| (void)name; |
| (void)index; |
| (void)origin; |
| (void)request; |
| (void)hout; |
| return CURLHE_NOT_BUILT_IN; |
| } |
| |
| struct curl_header *curl_easy_nextheader(CURL *easy, |
| unsigned int type, |
| int request, |
| struct curl_header *prev) |
| { |
| (void)easy; |
| (void)type; |
| (void)request; |
| (void)prev; |
| return NULL; |
| } |
| #endif |