curl: add --output-dir

Works with --create-dirs and with -J

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

Closes #5637
This commit is contained in:
Daniel Stenberg 2020-08-24 08:31:36 +02:00
parent 510d98157f
commit 5620d2cc78
No known key found for this signature in database
GPG Key ID: 5CC908FDB71E12C2
17 changed files with 381 additions and 24 deletions

View File

@ -159,7 +159,6 @@
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

View 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 \

View File

@ -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.

View File

@ -127,6 +127,7 @@
--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

View File

@ -39,6 +39,15 @@
#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 */

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

View 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;

View 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 */

View 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",

View 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 */

View 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

59
tests/data/test3008 Normal file
View File

@ -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
Host: %HOSTIP:%HTTPPORT
Accept: */*
</protocol>
<file name="log/3008">
-foo-
</file>
</verify>
</testcase>

59
tests/data/test3009 Normal file
View File

@ -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
Host: %HOSTIP:%HTTPPORT
Accept: */*
</protocol>
<errorcode>
23
</errorcode>
</verify>
</testcase>

59
tests/data/test3011 Normal file
View File

@ -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
Host: %HOSTIP:%HTTPPORT
Accept: */*
</protocol>
<file name="log/tmp/3011">
-foo-
</file>
</verify>
</testcase>

62
tests/data/test3012 Normal file
View File

@ -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
Host: %HOSTIP:%HTTPPORT
Accept: */*
</protocol>
<file name="log/MMM3012MMM">
-foo-
</file>
</verify>
</testcase>

69
tests/data/test3013 Normal file
View File

@ -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
Host: %HOSTIP:%HTTPPORT
Accept: */*
GET /another/3013 HTTP/1.1
Host: %HOSTIP:%HTTPPORT
Accept: */*
</protocol>
<file name="log/3013">
-foo-
</file>
<file2 name="log/second3013">
-foo-
</file2>
</verify>
</testcase>

View File

@ -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;
}