# | (__| |_| | _ <| |___
# \___|\___/|_| \_\_____|
#
-# Copyright (C) 1998 - 2021, Daniel Stenberg, <daniel@haxx.se>, et al.
+# Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
#
# This software is licensed as described in the file COPYING, which
# you should have received as part of this distribution. The terms
interface.d \
ipv4.d \
ipv6.d \
+ json.d \
junk-session-cookies.d \
keepalive-time.d \
key-type.d \
--- /dev/null
+Long: json
+Arg: <data>
+Help: HTTP POST JSON
+Protocols: HTTP
+See-also: data-binary data-raw
+Mutexed: form head upload-file
+Category: http post upload
+Example: --json '{ "drink": "coffe" }' $URL
+Example: --json '{ "drink":' --json ' "coffe" }' $URL
+Example: --json @prepared $URL
+Example: --json @- $URL < json.txt
+Added: 7.82.0
+---
+Sends the specified JSON data in a POST request to the HTTP server. --json
+works as a shortcut for passing on these three options:
+
+ --data [arg]
+ --header "Content-Type: application/json"
+ --header "Accept: application/json"
+
+There is **no verification** that the passed in data is actual JSON or that
+the syntax is correct.
+
+If you start the data with the letter @, the rest should be a file name to
+read the data from, or a single dash (-) if you want curl to read the data
+from stdin. Posting data from a file named \&'foobar' would thus be done with
+--json @foobar and to instead read the data from stdin, use --json @-.
+
+If this option is used more than once on the same command line, the additional
+data pieces will be concatenated to the previous before sending.
+
+The headers this option sets can be overriden with --header as usual.
--interface 7.3
--ipv4 (-4) 7.10.8
--ipv6 (-6) 7.10.8
+--json 7.82.0
--junk-session-cookies (-j) 7.9.7
--keepalive-time 7.18.0
--key 7.9.3
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
- * Copyright (C) 1998 - 2021, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
bool proxydigest;
bool proxybasic;
bool proxyanyauth;
+ bool jsoned; /* added json content-type */
char *writeout; /* %-styled format string to output */
struct curl_slist *quote;
struct curl_slist *postquote;
{"da", "data-ascii", ARG_STRING},
{"db", "data-binary", ARG_STRING},
{"de", "data-urlencode", ARG_STRING},
+ {"df", "json", ARG_STRING},
{"D", "dump-header", ARG_FILENAME},
{"e", "referer", ARG_STRING},
{"E", "cert", ARG_FILENAME},
size_t size = 0;
bool raw_mode = (subletter == 'r');
- if(subletter == 'e') { /* --data-urlencode*/
+ if(subletter == 'e') { /* --data-urlencode */
/* [name]=[content], we encode the content part only
* [name]@[file name]
*
"an empty POST.\n", nextarg);
}
- if(subletter == 'b')
+ if((subletter == 'b') || /* --data-binary */
+ (subletter == 'f') /* --json */)
/* forced binary */
err = file2memory(&postdata, &size, file);
else {
if(postdata)
size = strlen(postdata);
}
+ if(subletter == 'f')
+ config->jsoned = TRUE;
#ifdef CURL_DOES_CONVERSIONS
if(subletter != 'b') {
return PARAM_NO_MEM;
}
memcpy(config->postfields, oldpost, (size_t)oldlen);
- /* use byte value 0x26 for '&' to accommodate non-ASCII platforms */
- config->postfields[oldlen] = '\x26';
- memcpy(&config->postfields[oldlen + 1], postdata, size);
- config->postfields[oldlen + 1 + size] = '\0';
+ if(subletter != 'f') {
+ /* skip this treatment for --json */
+ /* use byte value 0x26 for '&' to accommodate non-ASCII platforms */
+ config->postfields[oldlen] = '\x26';
+ memcpy(&config->postfields[oldlen + 1], postdata, size);
+ config->postfields[oldlen + 1 + size] = '\0';
+ config->postfieldsize += size + 1;
+ }
+ else {
+ memcpy(&config->postfields[oldlen], postdata, size);
+ config->postfields[oldlen + size] = '\0';
+ config->postfieldsize += size;
+ }
Curl_safefree(oldpost);
Curl_safefree(postdata);
- config->postfieldsize += size + 1;
}
else {
config->postfields = postdata;
: NULL;
result = getparameter(orig_opt, nextarg, &passarg, global, config);
+
curlx_unicodefree(nextarg);
config = global->last;
if(result == PARAM_NEXT_OPERATION) {
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
- * Copyright (C) 1998 - 2021, Daniel Stenberg, <daniel.se>, et al.
+ * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel.se>, et al.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
{"-6, --ipv6",
"Resolve names to IPv6 addresses",
CURLHELP_CONNECTION | CURLHELP_DNS},
+ {" --json <data>",
+ "HTTP POST JSON",
+ CURLHELP_HTTP | CURLHELP_POST | CURLHELP_UPLOAD},
{"-j, --junk-session-cookies",
"Ignore session cookies read from file",
CURLHELP_HTTP},
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
- * Copyright (C) 1998 - 2021, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
return strdup(CURL_NAME "/" CURL_VERSION);
}
+#define isheadersep(x) ((((x)==':') || ((x)==';')))
+
+/*
+ * inlist() returns true if the given 'checkfor' header is present in the
+ * header list.
+ */
+static bool inlist(const struct curl_slist *head,
+ const char *checkfor)
+{
+ size_t thislen = strlen(checkfor);
+ DEBUGASSERT(thislen);
+ DEBUGASSERT(checkfor[thislen-1] != ':');
+
+ for(; head; head = head->next) {
+ if(curl_strnequal(head->data, checkfor, thislen) &&
+ isheadersep(head->data[thislen]) )
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
CURLcode get_args(struct OperationConfig *config, const size_t i)
{
CURLcode result = CURLE_OK;
bool last = (config->next ? FALSE : TRUE);
+ if(config->jsoned) {
+ ParameterError err = PARAM_OK;
+ /* --json also implies json Content-Type: and Accept: headers - if
+ they are not set with -H */
+ if(!inlist(config->headers, "Content-Type"))
+ err = add2list(&config->headers, "Content-Type: application/json");
+ if(!err && !inlist(config->headers, "Accept"))
+ err = add2list(&config->headers, "Accept: application/json");
+ if(err)
+ return CURLE_OUT_OF_MEMORY;
+ }
+
/* Check we have a password for the given host user */
if(config->userpwd && !config->oauth_bearer) {
result = checkpasswd("host", i, last, &config->userpwd);
test361 test362 test363 test364 test365 test366 test367 test368 test369 \
test370 test371 test372 test373 test374 \
\
-test380 test381 \
+test380 test381 test383 test384 test385 test386 \
+\
test392 test393 test394 test395 test396 test397 \
\
test400 test401 test402 test403 test404 test405 test406 test407 test408 \
--- /dev/null
+<testcase>
+<info>
+<keywords>
+HTTP
+HTTP POST
+--json
+</keywords>
+</info>
+#
+# Server-side
+<reply>
+<data>
+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>
+HTTP with --json
+ </name>
+ <command>
+--json '{ "drink": "coffe" }' http://%HOSTIP:%HTTPPORT/%TESTNUMBER
+</command>
+</client>
+
+#
+# Verify data after the test has been "shot"
+<verify>
+<protocol nonewline="yes">
+POST /%TESTNUMBER HTTP/1.1\r
+Host: %HOSTIP:%HTTPPORT\r
+User-Agent: curl/%VERSION\r
+Content-Type: application/json\r
+Accept: application/json\r
+Content-Length: 20\r
+\r
+{ "drink": "coffe" }
+</protocol>
+</verify>
+</testcase>
--- /dev/null
+<testcase>
+<info>
+<keywords>
+HTTP
+HTTP POST
+--json
+</keywords>
+</info>
+#
+# Server-side
+<reply>
+<data>
+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>
+HTTP with --json from stdin
+ </name>
+<stdin>
+{ "drink": "coffe" }
+</stdin>
+<command>
+--json @- http://%HOSTIP:%HTTPPORT/%TESTNUMBER -H "Accept: foobar/*"
+</command>
+</client>
+
+#
+# Verify data after the test has been "shot"
+<verify>
+<protocol>
+POST /%TESTNUMBER HTTP/1.1\r
+Host: %HOSTIP:%HTTPPORT\r
+User-Agent: curl/%VERSION\r
+Accept: foobar/*\r
+Content-Type: application/json\r
+Content-Length: 21\r
+\r
+{ "drink": "coffe" }
+</protocol>
+</verify>
+</testcase>
--- /dev/null
+<testcase>
+<info>
+<keywords>
+HTTP
+HTTP POST
+--json
+</keywords>
+</info>
+#
+# Server-side
+<reply>
+<data>
+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>
+HTTP with --json x 2
+ </name>
+ <command>
+--json '{ "drink": "coffe",' --json ' "crunch": "cookie" }' http://%HOSTIP:%HTTPPORT/%TESTNUMBER -H "Content-Type: drinks/hot"
+</command>
+</client>
+
+#
+# Verify data after the test has been "shot"
+<verify>
+<protocol nonewline="yes">
+POST /%TESTNUMBER HTTP/1.1\r
+Host: %HOSTIP:%HTTPPORT\r
+User-Agent: curl/%VERSION\r
+Content-Type: drinks/hot\r
+Accept: application/json\r
+Content-Length: 40\r
+\r
+{ "drink": "coffe", "crunch": "cookie" }
+</protocol>
+</verify>
+</testcase>
--- /dev/null
+<testcase>
+<info>
+<keywords>
+HTTP
+HTTP POST
+--json
+</keywords>
+</info>
+#
+# Server-side
+<reply>
+<data>
+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>
+<data2>
+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
+
+hello
+</data>
+</reply>
+
+#
+# Client-side
+<client>
+<server>
+http
+</server>
+ <name>
+HTTP with --json + --next
+ </name>
+ <command>
+--json '{ "drink": "coffe" }' http://%HOSTIP:%HTTPPORT/%TESTNUMBER --next http://%HOSTIP:%HTTPPORT/%TESTNUMBER0002
+</command>
+</client>
+
+#
+# Verify data after the test has been "shot"
+<verify>
+<protocol>
+POST /%TESTNUMBER HTTP/1.1\r
+Host: %HOSTIP:%HTTPPORT\r
+User-Agent: curl/%VERSION\r
+Content-Type: application/json\r
+Accept: application/json\r
+Content-Length: 20\r
+\r
+{ "drink": "coffe" }GET /%TESTNUMBER0002 HTTP/1.1\r
+Host: %HOSTIP:%HTTPPORT\r
+User-Agent: curl/%VERSION\r
+Accept: */*\r
+\r
+</protocol>
+</verify>
+</testcase>