/*************************************************************************** * _ _ ____ _ * Project ___| | | | _ \| | * / __| | | | |_) | | * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * * Copyright (C) Daniel Stenberg, , 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 "sendf.h" #include "curl_trc.h" #include "headers.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_node *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 tempting and then lead to the reserved bits not being reserved anymore. */ h->origin = (unsigned int)(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_node *e; struct Curl_llist_node *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 = Curl_llist_head(&data->state.httphdrs); e; e = Curl_node_next(e)) { hs = Curl_node_elem(e); if(curl_strequal(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 = Curl_llist_head(&data->state.httphdrs); e; e = Curl_node_next(e)) { hs = Curl_node_elem(e); if(curl_strequal(hs->name, name) && (hs->type & type) && (hs->request == request) && (match++ == nameindex)) { e_pick = e; break; } } if(!e) /* this should not 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_node *pick; struct Curl_llist_node *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 = Curl_node_next(pick); } else pick = Curl_llist_head(&data->state.httphdrs); if(pick) { /* make sure it is the next header of the desired type */ do { hs = Curl_node_elem(pick); if((hs->type & type) && (hs->request == request)) break; pick = Curl_node_next(pick); } while(pick); } if(!pick) /* no more headers available */ return NULL; hs = Curl_node_elem(pick); /* count number of occurrences of this name within the mask and figure out the index for the currently selected entry */ for(e = Curl_llist_head(&data->state.httphdrs); e; e = Curl_node_next(e)) { struct Curl_header_store *check = Curl_node_elem(e); if(curl_strequal(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 blank letters */ while(ISBLANK(*header)) header++; *value = header; /* skip all trailing space letters */ while((end > header) && ISBLANK(*end)) *end-- = 0; /* null-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, CR or LF * terminated. */ CURLcode Curl_headers_push(struct Curl_easy *data, const char *header, size_t hlen, /* length of header */ unsigned char type) { char *value = NULL; char *name = NULL; struct Curl_header_store *hs; CURLcode result = CURLE_OUT_OF_MEMORY; const size_t ilen = hlen; if((header[0] == '\r') || (header[0] == '\n')) /* ignore the body separator */ return CURLE_OK; /* trim off newline characters */ if(hlen && (header[hlen - 1] == '\n')) hlen--; if(hlen && (header[hlen - 1] == '\r')) hlen--; if(hlen == ilen) /* neither CR nor LF as terminator is not a valid header */ return CURLE_WEIRD_SERVER_REPLY; if(ISBLANK(header[0])) { /* pass leading blanks */ while(hlen && ISBLANK(*header)) { header++; hlen--; } if(!hlen) return CURLE_WEIRD_SERVER_REPLY; } if(Curl_llist_count(&data->state.httphdrs) >= MAX_HTTP_RESP_HEADER_COUNT) { failf(data, "Too many response headers, %d is max", MAX_HTTP_RESP_HEADER_COUNT); return CURLE_TOO_LARGE; } hs = curlx_calloc(1, sizeof(*hs) + hlen); if(!hs) return CURLE_OUT_OF_MEMORY; memcpy(hs->buffer, header, hlen); hs->buffer[hlen] = 0; /* null-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_append(&data->state.httphdrs, hs, &hs->node); data->state.prevhead = hs; } else { failf(data, "Invalid response header"); curlx_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, blen, htype); CURL_TRC_WRITE(data, "header_collect pushed(type=%x, len=%zu) -> %d", htype, blen, result); 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_node *e; struct Curl_llist_node *n; for(e = Curl_llist_head(&data->state.httphdrs); e; e = n) { struct Curl_header_store *hs = Curl_node_elem(e); n = Curl_node_next(e); curlx_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