From fd56befc9e7dcd9dd4325343b3e9080e7651d44d Mon Sep 17 00:00:00 2001 From: Jay Satiro Date: Sat, 6 Apr 2024 17:23:49 -0400 Subject: [PATCH] tool_cb_hdr: allow --remote-header-name for all http responses - Use the content-disposition filename (server-specified filename) for non-2xx responses. For example, a server may send a content-disposition filename header with a redirect reply (3xx) but not with the final response (2xx). Without this change curl would ignore the server's specified filename and continue to use the filename extracted from the user-specified URL. This is a partial revert of 75d79a44, which limited content-disposition and etag header processing to 2xx response codes. There's no explanation for why that was done. Reported-by: Morgan Willcock Fixes https://github.com/curl/curl/issues/13302 Closes #xxxx --- src/tool_cb_hdr.c | 136 +++++++++++++++++++++++----------------------- 1 file changed, 69 insertions(+), 67 deletions(-) diff --git a/src/tool_cb_hdr.c b/src/tool_cb_hdr.c index a64654eaf62b3e..109be12e39e17c 100644 --- a/src/tool_cb_hdr.c +++ b/src/tool_cb_hdr.c @@ -107,19 +107,20 @@ size_t tool_header_cb(char *ptr, size_t size, size_t nmemb, void *userdata) curl_easy_getinfo(per->curl, CURLINFO_SCHEME, &scheme); scheme = proto_token(scheme); + + /* + * Write etag to file when --etag-save option is given. + */ + if((scheme == proto_http || scheme == proto_https)) { long response = 0; curl_easy_getinfo(per->curl, CURLINFO_RESPONSE_CODE, &response); - if(response/100 != 2) - /* only care about these headers in 2xx responses */ - ; - /* - * Write etag to file when --etag-save option is given. - */ - else if(per->config->etag_save_file && etag_save->stream && - /* match only header that start with etag (case insensitive) */ - checkprefix("etag:", str)) { + if(per->config->etag_save_file && etag_save->stream && + /* match only header that start with etag (case insensitive) */ + checkprefix("etag:", str) && + /* only care about etag headers in 2xx responses */ + (response/100 == 2)) { const char *etag_h = &str[5]; const char *eot = end - 1; if(*eot == '\n') { @@ -137,76 +138,77 @@ size_t tool_header_cb(char *ptr, size_t size, size_t nmemb, void *userdata) } } } + } - /* - * This callback sets the filename where output shall be written when - * curl options --remote-name (-O) and --remote-header-name (-J) have - * been simultaneously given and additionally server returns an HTTP - * Content-Disposition header specifying a filename property. - */ + /* + * This callback sets the filename where output shall be written when + * curl options --remote-name (-O) and --remote-header-name (-J) have + * been simultaneously given and additionally server returns an HTTP + * Content-Disposition header specifying a filename property. + */ - else if(hdrcbdata->honor_cd_filename && - (cb > 20) && checkprefix("Content-disposition:", str)) { - const char *p = str + 20; + if(hdrcbdata->honor_cd_filename && + (cb > 20) && checkprefix("Content-disposition:", str) && + (scheme == proto_http || scheme == proto_https)) { + const char *p = str + 20; - /* look for the 'filename=' parameter - (encoded filenames (*=) are not supported) */ - for(;;) { - char *filename; - size_t len; + /* look for the 'filename=' parameter + (encoded filenames (*=) are not supported) */ + for(;;) { + char *filename; + size_t len; + + while((p < end) && *p && !ISALPHA(*p)) + p++; + if(p > end - 9) + break; - while((p < end) && *p && !ISALPHA(*p)) + if(memcmp(p, "filename=", 9)) { + /* no match, find next parameter */ + while((p < end) && *p && (*p != ';')) p++; - if(p > end - 9) + if((p < end) && *p) + continue; + else break; - - if(memcmp(p, "filename=", 9)) { - /* no match, find next parameter */ - while((p < end) && *p && (*p != ';')) - p++; - if((p < end) && *p) - continue; - else - break; + } + p += 9; + + /* this expression below typecasts 'cb' only to avoid + warning: signed and unsigned type in conditional expression + */ + len = (ssize_t)cb - (p - str); + filename = parse_filename(p, len); + if(filename) { + if(outs->stream) { + /* indication of problem, get out! */ + free(filename); + return CURL_WRITEFUNC_ERROR; } - p += 9; - - /* this expression below typecasts 'cb' only to avoid - warning: signed and unsigned type in conditional expression - */ - len = (ssize_t)cb - (p - str); - filename = parse_filename(p, len); - if(filename) { - if(outs->stream) { - /* indication of problem, get out! */ - free(filename); - return CURL_WRITEFUNC_ERROR; - } - - if(per->config->output_dir) { - outs->filename = aprintf("%s/%s", per->config->output_dir, - filename); - free(filename); - if(!outs->filename) - return CURL_WRITEFUNC_ERROR; - } - else - outs->filename = filename; - - outs->is_cd_filename = TRUE; - outs->s_isreg = TRUE; - outs->fopened = FALSE; - outs->alloc_filename = TRUE; - hdrcbdata->honor_cd_filename = FALSE; /* done now! */ - if(!tool_create_output_file(outs, per->config)) + + if(per->config->output_dir) { + outs->filename = aprintf("%s/%s", per->config->output_dir, filename); + free(filename); + if(!outs->filename) return CURL_WRITEFUNC_ERROR; } - break; + else + outs->filename = filename; + + outs->is_cd_filename = TRUE; + outs->s_isreg = TRUE; + outs->fopened = FALSE; + outs->alloc_filename = TRUE; + hdrcbdata->honor_cd_filename = FALSE; /* done now! */ + if(!tool_create_output_file(outs, per->config)) + return CURL_WRITEFUNC_ERROR; } - if(!outs->stream && !tool_create_output_file(outs, per->config)) - return CURL_WRITEFUNC_ERROR; + break; } + if(!outs->stream && !tool_create_output_file(outs, per->config)) + return CURL_WRITEFUNC_ERROR; } + if(hdrcbdata->config->writeout) { char *value = memchr(ptr, ':', cb); if(value) {