]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
curl: add --output-dir
authorDaniel Stenberg <daniel@haxx.se>
Mon, 24 Aug 2020 06:31:36 +0000 (08:31 +0200)
committerDaniel Stenberg <daniel@haxx.se>
Mon, 24 Aug 2020 20:41:37 +0000 (22:41 +0200)
Works with --create-dirs and with -J

Add test 3008, 3009, 3011, 3012 and 3013 to verify.

Closes #5637

17 files changed:
docs/TODO
docs/cmdline-opts/Makefile.inc
docs/cmdline-opts/output-dir.d [new file with mode: 0644]
docs/options-in-versions
src/tool_cb_wrt.c
src/tool_cfgable.c
src/tool_cfgable.h
src/tool_getparam.c
src/tool_help.c
src/tool_operate.c
tests/data/Makefile.inc
tests/data/test3008 [new file with mode: 0644]
tests/data/test3009 [new file with mode: 0644]
tests/data/test3011 [new file with mode: 0644]
tests/data/test3012 [new file with mode: 0644]
tests/data/test3013 [new file with mode: 0644]
tests/runtests.pl

index 4f9b57bf6f3738d92097da6fdb8a28abbbf2fafa..874dba874d293090f13d0b1cad059f135d233744 100644 (file)
--- a/docs/TODO
+++ b/docs/TODO
  18.19 expand ~/ in config files
  18.20 host name sections in config files
  18.21 retry on the redirected-to URL
- 18.22 Add flag to specify download directory
  18.23 Set the modification date on an uploaded file
  18.24 Use multiple parallel transfers for a single download
 
@@ -1122,12 +1121,6 @@ that doesn't exist on the server, just like --ftp-create-dirs.
 
  See https://github.com/curl/curl/issues/5462
 
-18.22 Add flag to specify download directory
-
- A directory name to basically prepend to the file name -O and -o use. Saves
- user from having to manually "cd" to the directory. Especially useful for
- command lines with multiple -O and different download directories.
-
 18.23 Set the modification date on an uploaded file
 
  For SFTP and posssibly FTP, curl could offer an option to set the
index 6a7b953bc2ea74b3e47de25c6667d7b89403f648..aa1acabe0702c6b14dfed28876f94a9d54b06a7a 100644 (file)
@@ -127,6 +127,7 @@ DPAGES =                                    \
   ntlm.d ntlm-wb.d                             \
   oauth2-bearer.d                              \
   output.d                                      \
+  output-dir.d                                  \
   parallel-immediate.d                          \
   parallel-max.d                                \
   parallel.d                                    \
diff --git a/docs/cmdline-opts/output-dir.d b/docs/cmdline-opts/output-dir.d
new file mode 100644 (file)
index 0000000..40bcb78
--- /dev/null
@@ -0,0 +1,18 @@
+Long: output-dir
+Arg: <dir>
+Help: Directory to save files in
+Added: 7.72.0
+See-also: remote-name remote-header-name
+---
+
+This option specifies the directory in which files should be stored, when
+--remote-name or --output are used.
+
+The given output directory is used for all URLs and output options on the
+command line, up until the first --next.
+
+If the specified target directory doesn't exist, the operation will fail
+unless --create-dirs is also used.
+
+If this option is used multiple times, the last specified directory will be
+used.
index 1a27306aecf17bdc4de5af0c0791214832c5f8fa..2945e137348d0ef756c89c713f56261bb3c2dcf6 100644 (file)
 --ntlm-wb                            7.22.0
 --oauth2-bearer                      7.33.0
 --output (-o)                        4.0
+--output-dir                         7.72.0
 --parallel (-Z)                      7.66.0
 --parallel-immediate                 7.68.0
 --parallel-max                       7.66.0
index 64b62fefd4a499e1d933e6c665f17816eef1b388..6fc51f9a5dd596fb982f4b6361524b5f4463f110 100644 (file)
 
 #include "memdebug.h" /* keep this as LAST include */
 
+#ifndef O_BINARY
+#define O_BINARY 0
+#endif
+#ifdef WIN32
+#define OPENMODE S_IREAD | S_IWRITE
+#else
+#define OPENMODE S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH
+#endif
+
 /* create a local file for writing, return TRUE on success */
 bool tool_create_output_file(struct OutStruct *outs,
                              struct OperationConfig *config)
@@ -55,21 +64,24 @@ bool tool_create_output_file(struct OutStruct *outs,
 
   if(outs->is_cd_filename) {
     /* don't overwrite existing files */
-#ifndef O_BINARY
-#define O_BINARY 0
-#endif
-    int fd = open(outs->filename, O_CREAT | O_WRONLY | O_EXCL | O_BINARY,
-#ifdef WIN32
-                  S_IREAD | S_IWRITE
-#else
-                  S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH
-#endif
-      );
+    int fd;
+    char *name = outs->filename;
+    char *aname = NULL;
+    if(config->output_dir) {
+      aname = aprintf("%s/%s", config->output_dir, name);
+      if(!aname) {
+        errorf(global, "out of memory\n");
+        return FALSE;
+      }
+      name = aname;
+    }
+    fd = open(name, O_CREAT | O_WRONLY | O_EXCL | O_BINARY, OPENMODE);
     if(fd != -1) {
       file = fdopen(fd, "wb");
       if(!file)
         close(fd);
     }
+    free(aname);
   }
   else
     /* open file for writing */
index 63bdeaa4616ffd22a2c5009621bbeb9ed9e56be0..e99602c4f3f2132ef7302657af96901e786da526 100644 (file)
@@ -89,6 +89,7 @@ static void free_config_fields(struct OperationConfig *config)
   Curl_safefree(config->mail_auth);
 
   Curl_safefree(config->netrc_file);
+  Curl_safefree(config->output_dir);
 
   urlnode = config->url_list;
   while(urlnode) {
index 4a90d0b725ee049d65d608051222e3d5a9706129..620bfef3ef449577dfc8e498520682364a6307b9 100644 (file)
@@ -80,6 +80,7 @@ struct OperationConfig {
   double connecttimeout;
   long maxredirs;
   curl_off_t max_filesize;
+  char *output_dir;
   char *headerfile;
   char *ftpport;
   char *iface;
index 0648c29b9720ec7c4fcc546a2fe71bec131f691f..74b6b7369925d875eab6b58e8626f55fd5518694 100644 (file)
@@ -303,6 +303,7 @@ static const struct LongShort aliases[]= {
   {"o",  "output",                   ARG_FILENAME},
   {"O",  "remote-name",              ARG_NONE},
   {"Oa", "remote-name-all",          ARG_BOOL},
+  {"Ob", "output-dir",               ARG_STRING},
   {"p",  "proxytunnel",              ARG_BOOL},
   {"P",  "ftp-port",                 ARG_STRING},
   {"q",  "disable",                  ARG_BOOL},
@@ -1911,6 +1912,10 @@ ParameterError getparameter(const char *flag, /* f or -long-flag */
         config->default_node_flags = toggle?GETOUT_USEREMOTE:0;
         break;
       }
+      else if(subletter == 'b') { /* --output-dir */
+        GetStr(&config->output_dir, nextarg);
+        break;
+      }
       /* FALLTHROUGH */
     case 'o': /* --output */
       /* output file */
index 0fc818d3c7e10a0d3a7b90680553fb6f63c604ea..29680d05ac150f7c8e42c1ea24ff7a8de659855d 100644 (file)
@@ -284,6 +284,8 @@ static const struct helptxt helptext[] = {
    "OAuth 2 Bearer Token"},
   {"-o, --output <file>",
    "Write to file instead of stdout"},
+  {"    --output-dir <dir>",
+   "Directory to save files in"},
   {"-Z, --parallel",
    "Perform transfers in parallel"},
   {"    --parallel-immediate",
index 1e4ed7df850f79ae23ff62f153e01788cba55243..aaadeeb9dd5e7753b1a70299abb2785be2c8cdc2 100644 (file)
@@ -1050,6 +1050,15 @@ static CURLcode single_transfer(struct GlobalConfig *global,
             }
           }
 
+          if(config->output_dir) {
+            char *d = aprintf("%s/%s", config->output_dir, per->outfile);
+            if(!d) {
+              result = CURLE_WRITE_ERROR;
+              break;
+            }
+            free(per->outfile);
+            per->outfile = d;
+          }
           /* Create the directory hierarchy, if not pre-existent to a multiple
              file output call */
 
index 29d913f620ea0ecd9611f0548ebafd3196ca12b0..c13fb7307810f6ae2f2219422ecbe88c96d2ae33 100644 (file)
@@ -224,5 +224,5 @@ test2078 \
 test2080 \
 test2100 \
 \
-test3000 test3001 \
-test3002 test3003 test3004 test3005 test3006 test3007 test3010
+test3000 test3001 test3002 test3003 test3004 test3005 test3006 test3007 \
+test3008 test3009 test3010 test3011 test3012 test3013
diff --git a/tests/data/test3008 b/tests/data/test3008
new file mode 100644 (file)
index 0000000..154ce20
--- /dev/null
@@ -0,0 +1,59 @@
+<testcase>
+<info>
+<keywords>
+-O
+</keywords>
+</info>
+#
+# Server-side
+<reply>
+<data nocheck="yes">
+HTTP/1.1 200 OK
+Date: Thu, 09 Nov 2010 14:49:00 GMT
+Server: test-server/fake
+Last-Modified: Tue, 13 Jun 2000 12:10:00 GMT
+ETag: "21025-dc7-39462498"
+Accept-Ranges: bytes
+Content-Length: 6
+Connection: close
+Content-Type: text/html
+Funny-head: yesyes
+
+-foo-
+</data>
+</reply>
+
+#
+# Client-side
+<client>
+<server>
+http
+</server>
+<features>
+http
+</features>
+<name>
+--output-dir
+</name>
+<command option="no-output,no-include">
+http://%HOSTIP:%HTTPPORT/this/is/the/3008 -O --output-dir %PWD/log
+</command>
+</client>
+
+#
+# Verify data after the test has been "shot"
+<verify>
+<strip>
+^User-Agent:.*
+</strip>
+<protocol>
+GET /this/is/the/3008 HTTP/1.1\r
+Host: %HOSTIP:%HTTPPORT\r
+Accept: */*\r
+\r
+</protocol>
+<file name="log/3008">
+-foo-
+</file>
+</verify>
+</testcase>
diff --git a/tests/data/test3009 b/tests/data/test3009
new file mode 100644 (file)
index 0000000..ec4bcea
--- /dev/null
@@ -0,0 +1,59 @@
+<testcase>
+<info>
+<keywords>
+-O
+</keywords>
+</info>
+#
+# Server-side
+<reply>
+<data nocheck="yes">
+HTTP/1.1 200 OK
+Date: Thu, 09 Nov 2010 14:49:00 GMT
+Server: test-server/fake
+Last-Modified: Tue, 13 Jun 2000 12:10:00 GMT
+ETag: "21025-dc7-39462498"
+Accept-Ranges: bytes
+Content-Length: 6
+Connection: close
+Content-Type: text/html
+Funny-head: yesyes
+
+-foo-
+</data>
+</reply>
+
+#
+# Client-side
+<client>
+<server>
+http
+</server>
+<features>
+http
+</features>
+<name>
+--output-dir a non-existing directory
+</name>
+<command option="no-output,no-include">
+http://%HOSTIP:%HTTPPORT/this/is/the/3009 -O --output-dir %PWD/not-there
+</command>
+</client>
+
+#
+# Verify data after the test has been "shot"
+<verify>
+<strip>
+^User-Agent:.*
+</strip>
+<protocol>
+GET /this/is/the/3009 HTTP/1.1\r
+Host: %HOSTIP:%HTTPPORT\r
+Accept: */*\r
+\r
+</protocol>
+<errorcode>
+23
+</errorcode>
+</verify>
+</testcase>
diff --git a/tests/data/test3011 b/tests/data/test3011
new file mode 100644 (file)
index 0000000..fcf7610
--- /dev/null
@@ -0,0 +1,59 @@
+<testcase>
+<info>
+<keywords>
+-O
+</keywords>
+</info>
+#
+# Server-side
+<reply>
+<data nocheck="yes">
+HTTP/1.1 200 OK
+Date: Thu, 09 Nov 2010 14:49:00 GMT
+Server: test-server/fake
+Last-Modified: Tue, 13 Jun 2000 12:10:00 GMT
+ETag: "21025-dc7-39462498"
+Accept-Ranges: bytes
+Content-Length: 6
+Connection: close
+Content-Type: text/html
+Funny-head: yesyes
+
+-foo-
+</data>
+</reply>
+
+#
+# Client-side
+<client>
+<server>
+http
+</server>
+<features>
+http
+</features>
+<name>
+--output-dir with --create-dirs
+</name>
+<command option="no-output,no-include">
+http://%HOSTIP:%HTTPPORT/this/is/the/3011 -O --output-dir %PWD/log/tmp --create-dirs
+</command>
+</client>
+
+#
+# Verify data after the test has been "shot"
+<verify>
+<strip>
+^User-Agent:.*
+</strip>
+<protocol>
+GET /this/is/the/3011 HTTP/1.1\r
+Host: %HOSTIP:%HTTPPORT\r
+Accept: */*\r
+\r
+</protocol>
+<file name="log/tmp/3011">
+-foo-
+</file>
+</verify>
+</testcase>
diff --git a/tests/data/test3012 b/tests/data/test3012
new file mode 100644 (file)
index 0000000..0a64faa
--- /dev/null
@@ -0,0 +1,62 @@
+<testcase>
+<info>
+<keywords>
+-O
+-J
+--output-dir
+</keywords>
+</info>
+#
+# Server-side
+<reply>
+<data nocheck="yes">
+HTTP/1.1 200 OK
+Date: Thu, 09 Nov 2010 14:49:00 GMT
+Server: test-server/fake
+Last-Modified: Tue, 13 Jun 2000 12:10:00 GMT
+ETag: "21025-dc7-39462498"
+Accept-Ranges: bytes
+Content-Length: 6
+Connection: close
+Content-Disposition: inline; filename="MMM3012MMM"
+Content-Type: text/html
+Funny-head: yesyes
+
+-foo-
+</data>
+</reply>
+
+#
+# Client-side
+<client>
+<server>
+http
+</server>
+<features>
+http
+</features>
+<name>
+--output-dir with -J
+</name>
+<command option="no-output,no-include">
+http://%HOSTIP:%HTTPPORT/this/is/the/3012 -OJ --output-dir %PWD/log
+</command>
+</client>
+
+#
+# Verify data after the test has been "shot"
+<verify>
+<strip>
+^User-Agent:.*
+</strip>
+<protocol>
+GET /this/is/the/3012 HTTP/1.1\r
+Host: %HOSTIP:%HTTPPORT\r
+Accept: */*\r
+\r
+</protocol>
+<file name="log/MMM3012MMM">
+-foo-
+</file>
+</verify>
+</testcase>
diff --git a/tests/data/test3013 b/tests/data/test3013
new file mode 100644 (file)
index 0000000..d2fcfa4
--- /dev/null
@@ -0,0 +1,69 @@
+<testcase>
+<info>
+<keywords>
+-O
+-J
+--output-dir
+</keywords>
+</info>
+#
+# Server-side
+<reply>
+<data nocheck="yes">
+HTTP/1.1 200 OK
+Date: Thu, 09 Nov 2010 14:49:00 GMT
+Server: test-server/fake
+Last-Modified: Tue, 13 Jun 2000 12:10:00 GMT
+ETag: "21025-dc7-39462498"
+Accept-Ranges: bytes
+Content-Length: 6
+Connection: close
+Content-Disposition: inline; filename="MMM3013MMM"
+Content-Type: text/html
+Funny-head: yesyes
+
+-foo-
+</data>
+</reply>
+
+#
+# Client-side
+<client>
+<server>
+http
+</server>
+<features>
+http
+</features>
+<name>
+Two --output-dir with --next in between
+</name>
+<command option="no-output,no-include">
+http://%HOSTIP:%HTTPPORT/this/is/the/3013 -O --output-dir %PWD/log http://%HOSTIP:%HTTPPORT/another/3013 -o second3013 --output-dir %PWD/log
+</command>
+</client>
+
+#
+# Verify data after the test has been "shot"
+<verify>
+<strip>
+^User-Agent:.*
+</strip>
+<protocol>
+GET /this/is/the/3013 HTTP/1.1\r
+Host: %HOSTIP:%HTTPPORT\r
+Accept: */*\r
+\r
+GET /another/3013 HTTP/1.1\r
+Host: %HOSTIP:%HTTPPORT\r
+Accept: */*\r
+\r
+</protocol>
+<file name="log/3013">
+-foo-
+</file>
+<file2 name="log/second3013">
+-foo-
+</file2>
+</verify>
+</testcase>
index b0bddba1c86032ef5f38096580a15f8a2fd586ac..3985f7fde36e5ffb493ba6bd67262fba065805ef 100755 (executable)
@@ -2713,15 +2713,21 @@ sub cleardir {
     my $file;
 
     # Get all files
-    opendir(DIR, $dir) ||
+    opendir(my $dh, $dir) ||
         return 0; # can't open dir
-    while($file = readdir(DIR)) {
-        if(($file !~ /^\.(|\.)$/)) {
-            unlink("$dir/$file");
+    while($file = readdir($dh)) {
+        if(($file !~ /^(\.|\.\.)\z/)) {
+            if(-d "$dir/$file") {
+                cleardir("$dir/$file");
+                rmdir("$dir/$file");
+            }
+            else {
+                unlink("$dir/$file");
+            }
             $count++;
         }
     }
-    closedir DIR;
+    closedir $dh;
     return $count;
 }