1
0
mirror of https://github.com/moparisthebest/curl synced 2024-08-13 17:03:50 -04:00

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.19 expand ~/ in config files
18.20 host name sections in config files 18.20 host name sections in config files
18.21 retry on the redirected-to URL 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.23 Set the modification date on an uploaded file
18.24 Use multiple parallel transfers for a single download 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 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 18.23 Set the modification date on an uploaded file
For SFTP and posssibly FTP, curl could offer an option to set the 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 \ ntlm.d ntlm-wb.d \
oauth2-bearer.d \ oauth2-bearer.d \
output.d \ output.d \
output-dir.d \
parallel-immediate.d \ parallel-immediate.d \
parallel-max.d \ parallel-max.d \
parallel.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 --ntlm-wb 7.22.0
--oauth2-bearer 7.33.0 --oauth2-bearer 7.33.0
--output (-o) 4.0 --output (-o) 4.0
--output-dir 7.72.0
--parallel (-Z) 7.66.0 --parallel (-Z) 7.66.0
--parallel-immediate 7.68.0 --parallel-immediate 7.68.0
--parallel-max 7.66.0 --parallel-max 7.66.0

View File

@ -39,6 +39,15 @@
#include "memdebug.h" /* keep this as LAST include */ #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 */ /* create a local file for writing, return TRUE on success */
bool tool_create_output_file(struct OutStruct *outs, bool tool_create_output_file(struct OutStruct *outs,
struct OperationConfig *config) struct OperationConfig *config)
@ -55,21 +64,24 @@ bool tool_create_output_file(struct OutStruct *outs,
if(outs->is_cd_filename) { if(outs->is_cd_filename) {
/* don't overwrite existing files */ /* don't overwrite existing files */
#ifndef O_BINARY int fd;
#define O_BINARY 0 char *name = outs->filename;
#endif char *aname = NULL;
int fd = open(outs->filename, O_CREAT | O_WRONLY | O_EXCL | O_BINARY, if(config->output_dir) {
#ifdef WIN32 aname = aprintf("%s/%s", config->output_dir, name);
S_IREAD | S_IWRITE if(!aname) {
#else errorf(global, "out of memory\n");
S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH return FALSE;
#endif }
); name = aname;
}
fd = open(name, O_CREAT | O_WRONLY | O_EXCL | O_BINARY, OPENMODE);
if(fd != -1) { if(fd != -1) {
file = fdopen(fd, "wb"); file = fdopen(fd, "wb");
if(!file) if(!file)
close(fd); close(fd);
} }
free(aname);
} }
else else
/* open file for writing */ /* 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->mail_auth);
Curl_safefree(config->netrc_file); Curl_safefree(config->netrc_file);
Curl_safefree(config->output_dir);
urlnode = config->url_list; urlnode = config->url_list;
while(urlnode) { while(urlnode) {

View File

@ -80,6 +80,7 @@ struct OperationConfig {
double connecttimeout; double connecttimeout;
long maxredirs; long maxredirs;
curl_off_t max_filesize; curl_off_t max_filesize;
char *output_dir;
char *headerfile; char *headerfile;
char *ftpport; char *ftpport;
char *iface; char *iface;

View File

@ -303,6 +303,7 @@ static const struct LongShort aliases[]= {
{"o", "output", ARG_FILENAME}, {"o", "output", ARG_FILENAME},
{"O", "remote-name", ARG_NONE}, {"O", "remote-name", ARG_NONE},
{"Oa", "remote-name-all", ARG_BOOL}, {"Oa", "remote-name-all", ARG_BOOL},
{"Ob", "output-dir", ARG_STRING},
{"p", "proxytunnel", ARG_BOOL}, {"p", "proxytunnel", ARG_BOOL},
{"P", "ftp-port", ARG_STRING}, {"P", "ftp-port", ARG_STRING},
{"q", "disable", ARG_BOOL}, {"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; config->default_node_flags = toggle?GETOUT_USEREMOTE:0;
break; break;
} }
else if(subletter == 'b') { /* --output-dir */
GetStr(&config->output_dir, nextarg);
break;
}
/* FALLTHROUGH */ /* FALLTHROUGH */
case 'o': /* --output */ case 'o': /* --output */
/* output file */ /* output file */

View File

@ -284,6 +284,8 @@ static const struct helptxt helptext[] = {
"OAuth 2 Bearer Token"}, "OAuth 2 Bearer Token"},
{"-o, --output <file>", {"-o, --output <file>",
"Write to file instead of stdout"}, "Write to file instead of stdout"},
{" --output-dir <dir>",
"Directory to save files in"},
{"-Z, --parallel", {"-Z, --parallel",
"Perform transfers in parallel"}, "Perform transfers in parallel"},
{" --parallel-immediate", {" --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 /* Create the directory hierarchy, if not pre-existent to a multiple
file output call */ file output call */

View File

@ -224,5 +224,5 @@ test2078 \
test2080 \ test2080 \
test2100 \ test2100 \
\ \
test3000 test3001 \ test3000 test3001 test3002 test3003 test3004 test3005 test3006 test3007 \
test3002 test3003 test3004 test3005 test3006 test3007 test3010 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; my $file;
# Get all files # Get all files
opendir(DIR, $dir) || opendir(my $dh, $dir) ||
return 0; # can't open dir return 0; # can't open dir
while($file = readdir(DIR)) { while($file = readdir($dh)) {
if(($file !~ /^\.(|\.)$/)) { if(($file !~ /^(\.|\.\.)\z/)) {
if(-d "$dir/$file") {
cleardir("$dir/$file");
rmdir("$dir/$file");
}
else {
unlink("$dir/$file"); unlink("$dir/$file");
}
$count++; $count++;
} }
} }
closedir DIR; closedir $dh;
return $count; return $count;
} }