]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
tool: support --show-headers AND --remote-header-name
authorDaniel Stenberg <daniel@haxx.se>
Mon, 30 Sep 2024 13:38:56 +0000 (15:38 +0200)
committerDaniel Stenberg <daniel@haxx.se>
Wed, 2 Oct 2024 06:04:33 +0000 (08:04 +0200)
By keeping the headers in memory until we know the target file name,
then output them all.

Previously this option combination would cause an error.

Add test 1310 and 1492 to verify. Adjusted test 1460 to work in the new
conditions.

Closes #15110

12 files changed:
src/tool_cb_hdr.c
src/tool_cb_hdr.h
src/tool_cb_wrt.c
src/tool_getparam.c
src/tool_getparam.h
src/tool_helpers.c
src/tool_operate.c
tests/data/DISABLED
tests/data/Makefile.am
tests/data/test1310 [new file with mode: 0644]
tests/data/test1460
tests/data/test1492 [new file with mode: 0644]

index 969acac1e4144c6c3cfd9e05f5dbe460483c5d84..1b0348d23cb7621aa4812c34e02d18bd95c51cfd 100644 (file)
@@ -62,6 +62,25 @@ static void write_linked_location(CURL *curl, const char *location,
     size_t loclen, FILE *stream);
 #endif
 
+int tool_write_headers(struct HdrCbData *hdrcbdata, FILE *stream)
+{
+  struct curl_slist *h = hdrcbdata->headlist;
+  int rc = 1;
+  while(h) {
+    /* not "handled", just show it */
+    size_t len = strlen(h->data);
+    if(len != fwrite(h->data, 1, len, stream))
+      goto fail;
+    h = h->next;
+  }
+  rc = 0; /* success */
+fail:
+  curl_slist_free_all(hdrcbdata->headlist);
+  hdrcbdata->headlist = NULL;
+  return rc;
+}
+
+
 /*
 ** callback for CURLOPT_HEADERFUNCTION
 */
@@ -164,63 +183,90 @@ size_t tool_header_cb(char *ptr, size_t size, size_t nmemb, void *userdata)
      * Content-Disposition header specifying a filename property.
      */
 
-    else if(hdrcbdata->honor_cd_filename &&
-            (cb > 20) && checkprefix("Content-disposition:", str)) {
-      const char *p = str + 20;
+    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;
+        /* 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 != ';'))
+          while((p < end) && *p && !ISALPHA(*p))
             p++;
-          if((p < end) && *p)
-            continue;
-          else
+          if(p > end - 9)
             break;
-        }
-        p += 9;
-
-        len = cb - (size_t)(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)
+          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! */
+              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))
+              return CURL_WRITEFUNC_ERROR;
+            if(tool_write_headers(&per->hdrcbdata, outs->stream))
               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))
+          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 zero terminated, we need an extra dance. */
+        char *clone = aprintf("%.*s", (int)cb, (char *)str);
+        if(clone) {
+          struct curl_slist *old = hdrcbdata->headlist;
+          hdrcbdata->headlist = curl_slist_append(old, clone);
+          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;
         }
-        break;
+        return cb; /* done for now */
       }
-      if(!outs->stream && !tool_create_output_file(outs, per->config))
-        return CURL_WRITEFUNC_ERROR;
     }
   }
   if(hdrcbdata->config->writeout) {
index a855052d0d3e65729ff305f3f62689b23661f95b..7402c12a9b91944887740e5890ce092dcf6ad03e 100644 (file)
@@ -46,9 +46,12 @@ struct HdrCbData {
   struct OutStruct *outs;
   struct OutStruct *heads;
   struct OutStruct *etag_save;
+  struct curl_slist *headlist;
   bool honor_cd_filename;
 };
 
+int tool_write_headers(struct HdrCbData *hdrcbdata, FILE *stream);
+
 /*
 ** callback for CURLOPT_HEADERFUNCTION
 */
index e35489a39f4c6e355a397dc87fc25475a16c9cb2..f25a481503479857ecae58499f6aeb112e5e10cb 100644 (file)
@@ -345,7 +345,13 @@ size_t tool_write_cb(char *buffer, size_t sz, size_t nmemb, void *userdata)
   }
   else
 #endif
+  {
+    if(per->hdrcbdata.headlist) {
+      if(tool_write_headers(&per->hdrcbdata, outs->stream))
+        return CURL_WRITEFUNC_ERROR;
+    }
     rc = fwrite(buffer, sz, nmemb, outs->stream);
+  }
 
   if(bytes == rc)
     /* we added this amount of data to the output */
index 7a38992a9e86a31e2d95384f4b828e4fb0cd665b..9a5de1e731e4773ba3f2db63168c0dda9ce5539f 100644 (file)
@@ -2763,9 +2763,7 @@ ParameterError parse_args(struct GlobalConfig *global, int argc,
   }
 
   if(!result && config->content_disposition) {
-    if(config->show_headers)
-      result = PARAM_CONTDISP_SHOW_HEADER;
-    else if(config->resume_from_current)
+    if(config->resume_from_current)
       result = PARAM_CONTDISP_RESUME_FROM;
   }
 
index 9d6c72ef825e8501dbb731af3fe6345fa4e63552..b22e60b7b34d95a82b9f38d1f238234975776aa3 100644 (file)
@@ -342,7 +342,6 @@ typedef enum {
   PARAM_NO_PREFIX,
   PARAM_NUMBER_TOO_LARGE,
   PARAM_NO_NOT_BOOLEAN,
-  PARAM_CONTDISP_SHOW_HEADER, /* --include and --remote-header-name */
   PARAM_CONTDISP_RESUME_FROM, /* --continue-at and --remote-header-name */
   PARAM_READ_ERROR,
   PARAM_EXPAND_ERROR, /* --expand problem */
index 2e15144b7b85de7d0667028f244fb16d7d49aaa9..02193c3e5151aabf944cc652368be255c55f7497 100644 (file)
@@ -67,8 +67,6 @@ const char *param2text(ParameterError error)
     return "too large number";
   case PARAM_NO_NOT_BOOLEAN:
     return "used '--no-' for option that is not a boolean";
-  case PARAM_CONTDISP_SHOW_HEADER:
-    return "showing headers and --remote-header-name cannot be combined";
   case PARAM_CONTDISP_RESUME_FROM:
     return "--continue-at and --remote-header-name cannot be combined";
   case PARAM_READ_ERROR:
index 5b51243374a24ccfcf121a882cd9415eeb79d959..0a959a8bcd2420b768d44a4f496c327c9a99d1d8 100644 (file)
@@ -770,7 +770,8 @@ skip:
   free(per->uploadfile);
   if(global->parallel)
     free(per->errorbuffer);
-
+  curl_slist_free_all(per->hdrcbdata.headlist);
+  per->hdrcbdata.headlist = NULL;
   return result;
 }
 
index 80f835d4b72dc284878493b6a7b9f65d06b59d23..45b07536586deca372f1cf1279dadfcacff3f423 100644 (file)
@@ -75,6 +75,7 @@
 # 1021 re-added here due to flakiness
 1021
 1417
+1460
 1533
 1540
 1591
index 90442d80e39ad573840bd531825a31569cae07a3..5b028a05b24d5d9c0490506d92daf3e015da2d53 100644 (file)
@@ -170,7 +170,7 @@ test1271 test1272 test1273 test1274 test1275 test1276 test1277 test1278 \
 test1279 test1280 test1281 test1282 test1283 test1284 test1285 test1286 \
 test1287 test1288 test1289 test1290 test1291 test1292 test1293 test1294 \
 test1295 test1296 test1297 test1298 test1299 test1300 test1301 test1302 \
-test1303 test1304 test1305 test1306 test1307 test1308 test1309          \
+test1303 test1304 test1305 test1306 test1307 test1308 test1309 test1310 \
 test1311 test1312 test1313 test1314 test1315 test1316 test1317 test1318 \
 test1319 test1320 test1321 test1322 test1323 test1324 test1325 test1326 \
 test1327 test1328 test1329 test1330 test1331 test1332 test1333 test1334 \
@@ -193,7 +193,7 @@ test1455 test1456 test1457 test1458 test1459 test1460 test1461 test1462 \
 test1463 test1464 test1465 test1466 test1467 test1468 test1469 test1470 \
 test1471 test1472 test1473 test1474 test1475 test1476 test1477 test1478 \
 test1479 test1480 test1481 test1482 test1483 test1484 test1485 test1486 \
-test1487 test1488 test1489 test1490 test1491 \
+test1487 test1488 test1489 test1490 test1491 test1492 \
 \
 test1500 test1501 test1502 test1503 test1504 test1505 test1506 test1507 \
 test1508 test1509 test1510 test1511 test1512 test1513 test1514 test1515 \
diff --git a/tests/data/test1310 b/tests/data/test1310
new file mode 100644 (file)
index 0000000..49bb0d3
--- /dev/null
@@ -0,0 +1,63 @@
+<testcase>
+<info>
+<keywords>
+HTTP
+HTTP GET
+-J
+--show-headers
+</keywords>
+</info>
+
+#
+<reply>
+<data nocheck="yes">
+HTTP/1.1 200 OK
+Date: Tue, 09 Nov 2010 14:49:00 GMT
+Server: test-server/fake
+Content-Length: 6
+Connection: close
+Content-Disposition: inline; filename="name%TESTNUMBER"
+Content-Type: text/html
+
+12345
+</data>
+</reply>
+
+#
+# Client-side
+<client>
+<server>
+http
+</server>
+<name>
+HTTP GET with -J + --show-headers
+</name>
+<command option="no-output,no-include">
+http://%HOSTIP:%HTTPPORT/junk -J -O --show-headers --output-dir %LOGDIR
+</command>
+</client>
+
+#
+# Verify data after the test has been "shot"
+<verify>
+<protocol>
+GET /junk HTTP/1.1\r
+Host: %HOSTIP:%HTTPPORT\r
+User-Agent: curl/%VERSION\r
+Accept: */*\r
+\r
+</protocol>
+<file name="%LOGDIR/name%TESTNUMBER">
+HTTP/1.1 200 OK
+Date: Tue, 09 Nov 2010 14:49:00 GMT
+Server: test-server/fake
+Content-Length: 6
+Connection: close
+Content-Disposition: inline; filename="name%TESTNUMBER"
+Content-Type: text/html
+
+12345
+</file>
+
+</verify>
+</testcase>
index 4e85082e384eb40cfae19dcb019490b18e7cc25b..7422d4b99b2061e896f39dfbc90eb3a568b6e34c 100644 (file)
@@ -11,33 +11,26 @@ HTTP GET
 <reply>
 <data nocheck="yes">
 HTTP/1.1 200 swsclose
-  12345
-fooo
-54 3 2 1
+Date: Tue, 09 Nov 2010 14:49:00 GMT
+Server: test-server/fake
 Content-Disposition: filename=name%TESTNUMBER; charset=funny; option=strange
+Content-Length: 4
 
+hej
 </data>
 </reply>
 
 #
 # Client-side
 <client>
-# this relies on the debug feature to allow us to set directory to store the
-# -J output in
-<features>
-Debug
-</features>
 <server>
 http
 </server>
 <name>
 HTTP GET with -Ji and Content-Disposition with existing file
 </name>
-<setenv>
-CURL_TESTDIR=%LOGDIR
-</setenv>
 <command option="no-output,no-include">
-http://%HOSTIP:%HTTPPORT/%TESTNUMBER -Ji -O
+http://%HOSTIP:%HTTPPORT/%TESTNUMBER -Ji -O --output-dir %LOGDIR
 </command>
 <file name="%LOGDIR/name%TESTNUMBER">
 initial content
@@ -47,9 +40,9 @@ initial content
 #
 # Verify data after the test has been "shot"
 <verify>
-# Warning: --include and --remote-header-name cannot be combined.
+# Warning: Failed to open the file log/name1460: File exists
 <errorcode>
-2
+23
 </errorcode>
 <file name="%LOGDIR/name%TESTNUMBER">
 initial content
diff --git a/tests/data/test1492 b/tests/data/test1492
new file mode 100644 (file)
index 0000000..2e7e1f3
--- /dev/null
@@ -0,0 +1,63 @@
+<testcase>
+<info>
+<keywords>
+HTTP
+HTTP GET
+-J
+--show-headers
+</keywords>
+</info>
+
+#
+<reply>
+<data nocheck="yes">
+HTTP/1.1 200 OK
+Date: Tue, 09 Nov 2010 14:49:00 GMT
+Server: test-server/fake
+Content-Length: 6
+Connection: close
+Content-Jisposition: inline; filename="name%TESTNUMBER"
+Content-Type: text/html
+
+12345
+</data>
+</reply>
+
+#
+# Client-side
+<client>
+<server>
+http
+</server>
+<name>
+HTTP GET with -J + --show-headers but no Content-Disposition:
+</name>
+<command option="no-output,no-include">
+http://%HOSTIP:%HTTPPORT/junk%TESTNUMBER -J -O --show-headers --output-dir %LOGDIR
+</command>
+</client>
+
+#
+# Verify data after the test has been "shot"
+<verify>
+<protocol>
+GET /junk%TESTNUMBER HTTP/1.1\r
+Host: %HOSTIP:%HTTPPORT\r
+User-Agent: curl/%VERSION\r
+Accept: */*\r
+\r
+</protocol>
+<file name="%LOGDIR/junk%TESTNUMBER">
+HTTP/1.1 200 OK
+Date: Tue, 09 Nov 2010 14:49:00 GMT
+Server: test-server/fake
+Content-Length: 6
+Connection: close
+Content-Jisposition: inline; filename="name%TESTNUMBER"
+Content-Type: text/html
+
+12345
+</file>
+
+</verify>
+</testcase>