curl: make %output{} in -w specify a file to write to

It can be used multiple times. Use %output{>>name} to append.

Add docs. Test 990 and 991 verify.

Idea: #11400
Suggested-by: ed0d2b2ce19451f2
Closes #11416
This commit is contained in:
Daniel Stenberg 2023-07-31 00:00:20 +02:00
parent 92ac5a8d59
commit 1032f56efa
No known key found for this signature in database
GPG Key ID: 5CC908FDB71E12C2
6 changed files with 175 additions and 5 deletions

View File

@ -25,10 +25,18 @@ output a newline by using \\n, a carriage return with \\r and a tab space with
The output will be written to standard output, but this can be switched to
standard error by using %{stderr}.
Output HTTP headers from the most recent request by using \fB%header{name}\fP
where \fBname\fP is the case insensitive name of the header (without the
trailing colon). The header contents are exactly as sent over the network,
with leading and trailing whitespace trimmed. Added in curl 7.84.0.
Output HTTP headers from the most recent request by using *%header{name}*
where *name* is the case insensitive name of the header (without the trailing
colon). The header contents are exactly as sent over the network, with leading
and trailing whitespace trimmed. Added in curl 7.84.0.
Select a specific target destination file to write the output to, by using
*%output{name}* where *name* is the full file name. The output following that
instruction is then written to that file. More than one *%output{}* instruction
can be specified in the same write-out argument. If the file name cannot be
created, curl will leave the output to the one used prior to the *%output{}*
instruction. Use *%output{>>name}* to append data to an existing file. Added in
curl 8.3.0.
.B NOTE:
In Windows the %-symbol is a special symbol used to expand environment

View File

@ -517,6 +517,7 @@ void ourWriteOut(struct OperationConfig *config, struct per_transfer *per,
bool done = FALSE;
struct curl_certinfo *certinfo;
CURLcode res = curl_easy_getinfo(per->curl, CURLINFO_CERTINFO, &certinfo);
bool fclose_stream = FALSE;
if(!writeinfo)
return;
@ -556,9 +557,15 @@ void ourWriteOut(struct OperationConfig *config, struct per_transfer *per,
done = TRUE;
break;
case VAR_STDOUT:
if(fclose_stream)
fclose(stream);
fclose_stream = FALSE;
stream = stdout;
break;
case VAR_STDERR:
if(fclose_stream)
fclose(stream);
fclose_stream = FALSE;
stream = stderr;
break;
case VAR_JSON:
@ -600,6 +607,36 @@ void ourWriteOut(struct OperationConfig *config, struct per_transfer *per,
else
fputs("%header{", stream);
}
else if(!strncmp("output{", &ptr[1], 7)) {
bool append = FALSE;
ptr += 8;
if((ptr[0] == '>') && (ptr[1] == '>')) {
append = TRUE;
ptr += 2;
}
end = strchr(ptr, '}');
if(end) {
char fname[512]; /* holds the longest file name */
size_t flen = end - ptr;
if(flen < sizeof(fname)) {
FILE *stream2;
memcpy(fname, ptr, flen);
fname[flen] = 0;
stream2 = fopen(fname, append? FOPEN_APPENDTEXT :
FOPEN_WRITETEXT);
if(stream2) {
/* only change if the open worked */
if(fclose_stream)
fclose(stream);
stream = stream2;
fclose_stream = TRUE;
}
}
ptr = end + 1;
}
else
fputs("%output{", stream);
}
else {
/* illegal syntax, then just output the characters that are used */
fputc('%', stream);
@ -632,4 +669,6 @@ void ourWriteOut(struct OperationConfig *config, struct per_transfer *per,
ptr++;
}
}
if(fclose_stream)
fclose(stream);
}

View File

@ -124,7 +124,7 @@ test952 test953 test954 test955 test956 test957 test958 test959 test960 \
test961 test962 test963 test964 test965 test966 test967 test968 test969 \
test970 test971 test972 test973 test974 test975 test976 test977 test978 \
test979 test980 test981 test982 test983 test984 test985 test986 test987 \
test988 test989 \
test988 test989 test990 test991 \
\
test1000 test1001 test1002 test1003 test1004 test1005 test1006 test1007 \
test1008 test1009 test1010 test1011 test1012 test1013 test1014 test1015 \

57
tests/data/test990 Normal file
View File

@ -0,0 +1,57 @@
<testcase>
<info>
<keywords>
HTTP
HTTP GET
-w
</keywords>
</info>
#
# Server-side
<reply>
<data crlf="yes">
HTTP/1.1 200 OK
Date: Tue, 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>
<name>
use -w %output{}
</name>
<command>
http://%HOSTIP:%HTTPPORT/%TESTNUMBER -w '%output{%LOGDIR/output}%{http_code}\n'
</command>
</client>
#
# Verify data after the test has been "shot"
<verify>
<protocol crlf="yes">
GET /%TESTNUMBER HTTP/1.1
Host: %HOSTIP:%HTTPPORT
User-Agent: curl/%VERSION
Accept: */*
</protocol>
<file name="%LOGDIR/output" mode="text">
200
</file>
</verify>
</testcase>

60
tests/data/test991 Normal file
View File

@ -0,0 +1,60 @@
<testcase>
<info>
<keywords>
HTTP
HTTP GET
-w
</keywords>
</info>
#
# Server-side
<reply>
<data crlf="yes">
HTTP/1.1 200 OK
Date: Tue, 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>
<name>
use -w %output{} append
</name>
<file name="%LOGDIR/output" nonewline="yes">
line one
</file>
<command>
http://%HOSTIP:%HTTPPORT/%TESTNUMBER -w '%output{>>%LOGDIR/output}%{http_code}'
</command>
</client>
#
# Verify data after the test has been "shot"
<verify>
<protocol crlf="yes">
GET /%TESTNUMBER HTTP/1.1
Host: %HOSTIP:%HTTPPORT
User-Agent: curl/%VERSION
Accept: */*
</protocol>
<file name="%LOGDIR/output" nonewline="yes">
line one200
</file>
</verify>
</testcase>

View File

@ -1558,6 +1558,12 @@ sub singletest_check {
@generated = @newgen;
}
if($hash{'nonewline'}) {
# cut off the final newline from the final line of the
# output data
chomp($outfile[-1]);
}
$res = compare($runnerid, $testnum, $testname, "output ($filename)",
\@generated, \@outfile);
if($res) {