]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
curl: named globs in output file name for upload glob references
authorDaniel Stenberg <daniel@haxx.se>
Tue, 21 Apr 2026 22:52:16 +0000 (00:52 +0200)
committerDaniel Stenberg <daniel@haxx.se>
Wed, 13 May 2026 08:07:50 +0000 (10:07 +0200)
Use parts of text from the upload filename field when that uses globbing
by giving it a name the same way we do it for URL globs. For example, if
you upload three files to a HTTP URL and want to save the corresponding
responses in separate files:

    curl -T 'file{<num>1,2,3}' https://upload.example/ -o 'response-#<num>'

Verified by test 2014

Closes #21407

docs/cmdline-opts/output.md
docs/cmdline-opts/upload-file.md
src/tool_operate.c
src/tool_urlglob.c
src/tool_urlglob.h
tests/data/Makefile.am
tests/data/test2014 [new file with mode: 0644]

index c1d823e3244de6b3c7baafccc11878bfc103b414..b2d196d038dab626646f4f005720fb7c873dfc07 100644 (file)
@@ -23,10 +23,10 @@ Example:
 
 # `--output`
 
-Write output to the given file instead of stdout. If you are using globbing to
-fetch multiple documents, you should quote the URL and you can use `#`
-followed by a number in the filename. That variable is then replaced with the
-current string for the URL being fetched. Like in:
+Write output to the given file instead of stdout. If you are using globbing in
+the URL to fetch multiple documents, you should quote the URL and you can use
+`#` followed by a number in the filename. That variable gets replaced with the
+current glob text. Like in:
 
     curl "http://{one,two}.example.com" -o "file_#1.txt"
 
@@ -70,9 +70,9 @@ override curl's internal binary output in terminal prevention:
 Note that the binary output may be caused by the response being compressed, in
 which case you may want to use the --compressed option.
 
-Starting in curl 8.21.0, the separate globbing parts can be named and
-referenced by their names. The case sensitive alphanumeric name is set
-enclosed within angle brackets after the opening character. Examples:
+Since curl 8.21.0, the separate globbing parts can be named and referenced by
+their names. The case sensitive alphanumeric name is set enclosed within angle
+brackets after the opening character. Examples:
 
     curl "https://fun.example/{<num>one,two}.jpg" -o "save-#<num>"
 
@@ -80,3 +80,11 @@ enclosed within angle brackets after the opening character. Examples:
       -o "save-#<range>.txt"
 
 Referencing a named glob that is not set, causes an error.
+
+Since curl 8.21.0, you can use parts of the upload filename when it uses
+globbing by setting a glob name and referencing it the same way you reference
+named URL globs. For example, if you upload three files to a single fixed HTTP
+URL and want to save the corresponding responses in separate files:
+
+    curl -T 'file{<num>1,2,3}' \
+      https://upload.example/ -o 'response-#<num>'
index 5a2842e58ad80628eb9e1e3050e156cba2764df9..1988d8afb728baf83a4f68ad73a8f9df537759d8 100644 (file)
@@ -26,13 +26,13 @@ Upload the specified local file to the remote URL.
 
 If there is no file part in the specified URL, curl appends the local file
 name to the end of the URL before the operation starts. You must use a
-trailing slash (/) on the last directory to prove to curl that there is no
+trailing slash (`/`) on the last directory to prove to curl that there is no
 filename or curl thinks that your last directory name is the remote filename
 to use.
 
 When putting the local filename at the end of the URL, curl ignores what is on
-the left side of any slash (/) or backslash (\\) used in the filename and only
-appends what is on the right side of the rightmost such character.
+the left side of any slash (`/`) or backslash (`\\`) used in the filename and
+only appends what is on the right side of the rightmost such character.
 
 Use the filename `-` (a single dash) to use stdin instead of a given file.
 Alternately, the filename `.` (a single period) may be specified instead of
@@ -45,9 +45,19 @@ You can specify one --upload-file for each URL on the command line. Each
 --upload-file + URL pair specifies what to upload and to where. curl also
 supports globbing of the --upload-file argument, meaning that you can upload
 multiple files to a single URL by using the same URL globbing style supported
-in the URL.
+in the URL. Example:
 
-When uploading to an SMTP server: the uploaded data is assumed to be RFC 5322
-formatted. It has to feature the necessary set of headers and mail body
-formatted correctly by the user as curl does not transcode nor encode it
-further in any way.
+    curl --upload-file 'file{1,2,3}' ftp://ftp.example/
+
+Since curl 8.21.0, you can use parts of the upload filename when it uses
+globbing by setting a glob name and referencing that in the same way you
+reference named URL globs. For example, if you upload three files to a single
+fixed HTTP URL and want to save the corresponding responses in separate files:
+
+    curl -T 'file{<num>1,2,3}' \
+      https://upload.example/ -o 'response-#<num>'
+
+When uploading to an SMTP server (aka "sending email"): the uploaded data is
+assumed to be RFC 5322 formatted. It has to feature the necessary set of
+headers and mail body formatted correctly by the user as curl does not
+transcode nor encode it further in any way.
index 62d40afd55e3ac025dbd9da872e75bd5b9ca2e9c..c5ac095cb9104d17a58744ee99de737bc106b277 100644 (file)
@@ -1052,11 +1052,13 @@ static CURLcode setup_outfile(struct OperationConfig *config,
       return result;
     }
   }
-  else if(glob_inuse(&state->urlglob)) {
-    /* fill '#1' ... '#9' terms from URL pattern */
+  else if(glob_inuse(&state->urlglob) || glob_inuse(&state->inglob)) {
+    /* expand '#1' ... '#9' references from URL pattern and named references
+       from the upload file glob */
     SANITIZEcode sc;
     CURLcode result =
-      glob_match_url(&per->outfile, u->outfile, &state->urlglob, &sc);
+      glob_match_url(&per->outfile, u->outfile, &state->urlglob,
+                     glob_inuse(&state->inglob) ? &state->inglob : NULL, &sc);
 
     if(sc) {
       if(sc == SANITIZE_ERR_OUT_OF_MEMORY)
index a0dbb0bb6f2e9ffb6fed7464be008920f0c01b6e..dd7a6a9d8ef0d295010e51ba3caad4bf182d8865 100644 (file)
@@ -703,7 +703,8 @@ CURLcode glob_next_url(char **globbed, struct URLGlob *glob)
 #define MAX_OUTPUT_GLOB_LENGTH (1024 * 1024)
 
 CURLcode glob_match_url(char **output, const char *filename,
-                        struct URLGlob *glob, SANITIZEcode *sc)
+                        struct URLGlob *glob, struct URLGlob *glob2,
+                        SANITIZEcode *sc)
 {
   struct dynbuf dyn;
   const char *ifilename = filename;
@@ -741,7 +742,11 @@ CURLcode glob_match_url(char **output, const char *filename,
       if(!curlx_str_until(&filename, &name, MAX_GLOBNAME_LEN, '>') &&
          !curlx_str_single(&filename, '>')) {
         /* find the correct glob entry */
-        pat = glob_find_name(glob, &name);
+        if(glob_inuse(glob))
+          pat = glob_find_name(glob, &name);
+        if(!pat && glob2 && glob_inuse(glob2))
+          /* scan the second glob list if there is one */
+          pat = glob_find_name(glob2, &name);
         if(!pat) {
           /* when the name is given correctly, it needs to be an existing glob
              name, which makes this an error */
index abc279de73db32c06f9041cc67bce1875d009a5f..e891258aa46e5794f21b9c89cb3291ea5172f9e6 100644 (file)
@@ -79,7 +79,8 @@ CURLcode glob_url(struct URLGlob *glob, const char *url, curl_off_t *urlnum,
                   FILE *error);
 CURLcode glob_next_url(char **globbed, struct URLGlob *glob);
 CURLcode glob_match_url(char **output, const char *filename,
-                        struct URLGlob *glob, SANITIZEcode *sc);
+                        struct URLGlob *glob, struct URLGlob *glob2,
+                        SANITIZEcode *sc);
 void glob_cleanup(struct URLGlob *glob);
 bool glob_inuse(struct URLGlob *glob);
 
index 8dcf2d360c94ce416cb80a54b1b45b00ff9d3d23..b63c9c06a0c93286eb10a3539ae98f61459a9a5b 100644 (file)
@@ -245,7 +245,7 @@ test1970 test1971 test1972 test1973 test1974 test1975 test1976 test1977 \
 test1978 test1979 test1980 test1981 test1982 test1983 test1984 \
 \
 test2000 test2001 test2002 test2003 test2004 test2005 test2006 test2007 \
-test2008 test2009 test2010 test2011 test2012 test2013 \
+test2008 test2009 test2010 test2011 test2012 test2013 test2014 \
 \
                                                                test2023 \
 test2024 test2025 test2026 test2027 test2028 test2029 test2030 test2031 \
diff --git a/tests/data/test2014 b/tests/data/test2014
new file mode 100644 (file)
index 0000000..a7fb078
--- /dev/null
@@ -0,0 +1,97 @@
+<?xml version="1.0" encoding="US-ASCII"?>
+<testcase>
+<info>
+<keywords>
+HTTP
+HTTP PUT
+</keywords>
+</info>
+
+# Server-side
+<reply>
+<data crlf="headers" nocheck="yes">
+HTTP/1.1 200 OK swsbounce
+Date: Tue, 09 Nov 2010 14:49:00 GMT
+Server: test-server/fake
+Accept-Ranges: bytes
+Content-Length: 6
+Content-Type: text/html
+
+-foo-
+</data>
+<data1 crlf="headers" nocheck="yes">
+HTTP/1.1 200 OK
+Date: Tue, 09 Nov 2010 14:49:00 GMT
+Content-Length: 20
+Content-Type: text/html
+
+the second response
+</data1>
+</reply>
+
+# Client-side
+<client>
+<server>
+http
+</server>
+<name>
+upload with glob, output name based on upload glob
+</name>
+<command option="no-output">
+-T '%LOGDIR/upload{%LThej%GT1,2}' http://%HOSTIP:%HTTPPORT/%TESTNUMBER --silent '--output=%LOGDIR/out-#%LThej%GT'
+</command>
+
+<file name="%LOGDIR/upload1">
+first!
+</file>
+
+<file2 name="%LOGDIR/upload2">
+second
+</file2>
+
+</client>
+
+# Verify data after the test has been "shot"
+<verify>
+<protocol crlf="headers">
+PUT /%TESTNUMBER HTTP/1.1
+Host: %HOSTIP:%HTTPPORT
+User-Agent: curl/%VERSION
+Accept: */*
+Content-Length: 7
+
+first!
+PUT /%TESTNUMBER HTTP/1.1
+Host: %HOSTIP:%HTTPPORT
+User-Agent: curl/%VERSION
+Accept: */*
+Content-Length: 7
+
+second
+</protocol>
+<stdout>
+%EMPTY
+</stdout>
+
+<file name="%LOGDIR/out-1" crlf="headers">
+HTTP/1.1 200 OK swsbounce
+Date: Tue, 09 Nov 2010 14:49:00 GMT
+Server: test-server/fake
+Accept-Ranges: bytes
+Content-Length: 6
+Content-Type: text/html
+
+-foo-
+</file>
+
+<file2 name="%LOGDIR/out-2" crlf="headers">
+HTTP/1.1 200 OK
+Date: Tue, 09 Nov 2010 14:49:00 GMT
+Content-Length: 20
+Content-Type: text/html
+
+the second response
+</file2>
+
+</verify>
+</testcase>