From: Daniel Stenberg Date: Tue, 13 Jan 2026 13:02:49 +0000 (+0100) Subject: tool_cb_hdr: move etag and content-disposition logic into funcs X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=2b12dbc1165a3cb005d0e21fb0340bd69aaf0ffa;p=thirdparty%2Fcurl.git tool_cb_hdr: move etag and content-disposition logic into funcs Co-authored-by: Jay Satiro Closes #20288 --- diff --git a/src/tool_cb_hdr.c b/src/tool_cb_hdr.c index e65e617a15..c19f054075 100644 --- a/src/tool_cb_hdr.c +++ b/src/tool_cb_hdr.c @@ -239,6 +239,141 @@ fail: return rc; } +/* + * Write etag to file when --etag-save option is given. + */ +static size_t save_etag(const char *etag_h, const char *endp, + struct OutStruct *etag_save) +{ + const char *eot = endp - 1; + if(*eot == '\n') { + while(ISBLANK(*etag_h) && (etag_h < eot)) + etag_h++; + while(ISSPACE(*eot)) + eot--; + + if(eot >= etag_h) { + size_t etag_length = eot - etag_h + 1; + /* + * Truncate the etag save stream, it can have an existing etag value. + */ +#ifdef HAVE_FTRUNCATE + if(ftruncate(fileno(etag_save->stream), 0)) { + return CURL_WRITEFUNC_ERROR; + } +#else + if(fseek(etag_save->stream, 0, SEEK_SET)) { + return CURL_WRITEFUNC_ERROR; + } +#endif + + fwrite(etag_h, 1, etag_length, etag_save->stream); + /* terminate with newline */ + fputc('\n', etag_save->stream); + (void)fflush(etag_save->stream); + } + } + return 0; /* ok */ +} + +/* + * This function 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. + */ +static size_t content_disposition(const char *str, const char *end, + size_t cb, struct per_transfer *per) +{ + struct HdrCbData *hdrcbdata = &per->hdrcbdata; + struct OutStruct *outs = &per->outs; + + if((cb > 20) && checkprefix("Content-disposition:", str)) { + const char *p = str + 20; + /* 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; + + 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; + + len = cb - (size_t)(p - str); + filename = parse_filename(p, len); + if(filename) { + if(outs->stream) { + /* indication of problem, get out! */ + curlx_free(filename); + return CURL_WRITEFUNC_ERROR; + } + + if(per->config->output_dir) { + outs->filename = curl_maprintf("%s/%s", per->config->output_dir, + filename); + curlx_free(filename); + if(!outs->filename) + return CURL_WRITEFUNC_ERROR; + } + else + outs->filename = filename; + + outs->is_cd_filename = TRUE; + outs->regular_file = 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(tool_write_headers(&per->hdrcbdata, outs->stream)) + return CURL_WRITEFUNC_ERROR; + } + break; + } + if(!outs->stream && !tool_create_output_file(outs, per->config)) + return CURL_WRITEFUNC_ERROR; + if(tool_write_headers(&per->hdrcbdata, outs->stream)) + return CURL_WRITEFUNC_ERROR; + } /* content-disposition handling */ + + if(hdrcbdata->honor_cd_filename && + hdrcbdata->config->show_headers) { + /* still awaiting the Content-Disposition header, store the header in + memory. Since it is not null-terminated, we need an extra dance. */ + char *clone = curl_maprintf("%.*s", (int)cb, str); + if(clone) { + struct curl_slist *old = hdrcbdata->headlist; + hdrcbdata->headlist = curl_slist_append(old, clone); + curlx_free(clone); + if(!hdrcbdata->headlist) { + curl_slist_free_all(old); + return CURL_WRITEFUNC_ERROR; + } + } + else { + curl_slist_free_all(hdrcbdata->headlist); + hdrcbdata->headlist = NULL; + return CURL_WRITEFUNC_ERROR; + } + return cb; /* done for now */ + } + + return 0; /* ok */ +} + /* ** callback for CURLOPT_HEADERFUNCTION * @@ -297,134 +432,23 @@ size_t tool_header_cb(char *ptr, size_t size, size_t nmemb, void *userdata) /* only care about etag and content-disposition headers in 2xx and 3xx 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)) { - const char *etag_h = &str[5]; - const char *eot = end - 1; - if(*eot == '\n') { - while(ISBLANK(*etag_h) && (etag_h < eot)) - etag_h++; - while(ISSPACE(*eot)) - eot--; - - if(eot >= etag_h) { - size_t etag_length = eot - etag_h + 1; - /* - * Truncate the etag save stream, it can have an existing etag value. - */ -#ifdef HAVE_FTRUNCATE - if(ftruncate(fileno(etag_save->stream), 0)) { - return CURL_WRITEFUNC_ERROR; - } -#else - if(fseek(etag_save->stream, 0, SEEK_SET)) { - return CURL_WRITEFUNC_ERROR; - } -#endif - - fwrite(etag_h, 1, etag_length, etag_save->stream); - /* terminate with newline */ - fputc('\n', etag_save->stream); - (void)fflush(etag_save->stream); - } - } + size_t rc = save_etag(&str[5], end, etag_save); + if(rc) + return rc; } - /* - * 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. - */ - + /* Parse the content-disposition header. When honor_cd_filename is true + other headers may be stored until the content-disposition header is + reached, at which point the saved headers can be written. That means + the content_disposition() may return an rc when it has saved a + different header for writing later. */ else if(hdrcbdata->honor_cd_filename) { - if((cb > 20) && checkprefix("Content-disposition:", str)) { - const char *p = str + 20; - - /* 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; - - 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; - - len = cb - (size_t)(p - str); - filename = parse_filename(p, len); - if(filename) { - if(outs->stream) { - /* indication of problem, get out! */ - curlx_free(filename); - return CURL_WRITEFUNC_ERROR; - } - - if(per->config->output_dir) { - outs->filename = curl_maprintf("%s/%s", per->config->output_dir, - filename); - curlx_free(filename); - if(!outs->filename) - return CURL_WRITEFUNC_ERROR; - } - else - outs->filename = filename; - - outs->is_cd_filename = TRUE; - outs->regular_file = 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(tool_write_headers(&per->hdrcbdata, outs->stream)) - return CURL_WRITEFUNC_ERROR; - } - break; - } - if(!outs->stream && !tool_create_output_file(outs, per->config)) - return CURL_WRITEFUNC_ERROR; - if(tool_write_headers(&per->hdrcbdata, outs->stream)) - return CURL_WRITEFUNC_ERROR; - } /* content-disposition handling */ - - if(hdrcbdata->honor_cd_filename && - hdrcbdata->config->show_headers) { - /* still awaiting the Content-Disposition header, store the header in - memory. Since it is not null-terminated, we need an extra dance. */ - char *clone = curl_maprintf("%.*s", (int)cb, str); - if(clone) { - struct curl_slist *old = hdrcbdata->headlist; - hdrcbdata->headlist = curl_slist_append(old, clone); - curlx_free(clone); - if(!hdrcbdata->headlist) { - curl_slist_free_all(old); - return CURL_WRITEFUNC_ERROR; - } - } - else { - curl_slist_free_all(hdrcbdata->headlist); - hdrcbdata->headlist = NULL; - return CURL_WRITEFUNC_ERROR; - } - return cb; /* done for now */ - } + size_t rc = content_disposition(str, end, cb, per); + if(rc) + return rc; } } if(hdrcbdata->config->writeout) {