tool_cb_hdr: move etag and content-disposition logic into funcs

Co-authored-by: Jay Satiro
Closes #20288
This commit is contained in:
Daniel Stenberg 2026-01-13 14:02:49 +01:00
parent c6cd54d3a2
commit 2b12dbc116
No known key found for this signature in database
GPG key ID: 5CC908FDB71E12C2

View file

@ -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) {