diff --git a/docs/cmdline-opts/write-out.d b/docs/cmdline-opts/write-out.d
index 9f7181f3a2..c3ac7d8075 100644
--- a/docs/cmdline-opts/write-out.d
+++ b/docs/cmdline-opts/write-out.d
@@ -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
diff --git a/src/tool_writeout.c b/src/tool_writeout.c
index 2509d18b1a..0fc955d5b4 100644
--- a/src/tool_writeout.c
+++ b/src/tool_writeout.c
@@ -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);
}
diff --git a/tests/data/Makefile.inc b/tests/data/Makefile.inc
index 6dab156c0e..3d2c51c4ac 100644
--- a/tests/data/Makefile.inc
+++ b/tests/data/Makefile.inc
@@ -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 \
diff --git a/tests/data/test990 b/tests/data/test990
new file mode 100644
index 0000000000..1b22d1aa92
--- /dev/null
+++ b/tests/data/test990
@@ -0,0 +1,57 @@
+
+
+
+HTTP
+HTTP GET
+-w
+
+
+
+#
+# Server-side
+
+
+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-
+
+
+
+#
+# Client-side
+
+
+http
+
+
+use -w %output{}
+
+
+http://%HOSTIP:%HTTPPORT/%TESTNUMBER -w '%output{%LOGDIR/output}%{http_code}\n'
+
+
+
+#
+# Verify data after the test has been "shot"
+
+
+GET /%TESTNUMBER HTTP/1.1
+Host: %HOSTIP:%HTTPPORT
+User-Agent: curl/%VERSION
+Accept: */*
+
+
+
+200
+
+
+
diff --git a/tests/data/test991 b/tests/data/test991
new file mode 100644
index 0000000000..5a2cc62f30
--- /dev/null
+++ b/tests/data/test991
@@ -0,0 +1,60 @@
+
+
+
+HTTP
+HTTP GET
+-w
+
+
+
+#
+# Server-side
+
+
+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-
+
+
+
+#
+# Client-side
+
+
+http
+
+
+use -w %output{} append
+
+
+line one
+
+
+http://%HOSTIP:%HTTPPORT/%TESTNUMBER -w '%output{>>%LOGDIR/output}%{http_code}'
+
+
+
+#
+# Verify data after the test has been "shot"
+
+
+GET /%TESTNUMBER HTTP/1.1
+Host: %HOSTIP:%HTTPPORT
+User-Agent: curl/%VERSION
+Accept: */*
+
+
+
+line one200
+
+
+
diff --git a/tests/runtests.pl b/tests/runtests.pl
index c24c3598be..050f40e787 100755
--- a/tests/runtests.pl
+++ b/tests/runtests.pl
@@ -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) {