2
0
mirror of https://github.com/curl/curl.git synced 2025-03-31 16:00:35 +08:00

tool_getparam: initial --json support

Adds these test cases:

 383 - simple single command line option
 384 - reading it from stdin
 385 - getting two --json options on command line
 386 - --next works after --json

Closes 
This commit is contained in:
Daniel Stenberg 2022-01-21 09:38:44 +01:00
parent 1ce1f0b5a0
commit 32160cae84
No known key found for this signature in database
GPG Key ID: 5CC908FDB71E12C2
12 changed files with 343 additions and 12 deletions

@ -5,7 +5,7 @@
# | (__| |_| | _ <| |___
# \___|\___/|_| \_\_____|
#
# 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
@ -111,6 +111,7 @@ DPAGES = \
interface.d \
ipv4.d \
ipv6.d \
json.d \
junk-session-cookies.d \
keepalive-time.d \
key-type.d \

32
docs/cmdline-opts/json.d Normal file

@ -0,0 +1,32 @@
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.

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

@ -7,7 +7,7 @@
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
* 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
@ -189,6 +189,7 @@ struct OperationConfig {
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;

@ -230,6 +230,7 @@ static const struct LongShort aliases[]= {
{"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},
@ -1386,7 +1387,7 @@ ParameterError getparameter(const char *flag, /* f or -long-flag */
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]
*
@ -1489,7 +1490,8 @@ ParameterError getparameter(const char *flag, /* f or -long-flag */
"an empty POST.\n", nextarg);
}
if(subletter == 'b')
if((subletter == 'b') || /* --data-binary */
(subletter == 'f') /* --json */)
/* forced binary */
err = file2memory(&postdata, &size, file);
else {
@ -1516,6 +1518,8 @@ ParameterError getparameter(const char *flag, /* f or -long-flag */
if(postdata)
size = strlen(postdata);
}
if(subletter == 'f')
config->jsoned = TRUE;
#ifdef CURL_DOES_CONVERSIONS
if(subletter != 'b') {
@ -1540,13 +1544,21 @@ ParameterError getparameter(const char *flag, /* f or -long-flag */
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;
@ -2367,6 +2379,7 @@ ParameterError parse_args(struct GlobalConfig *global, int argc,
: NULL;
result = getparameter(orig_opt, nextarg, &passarg, global, config);
curlx_unicodefree(nextarg);
config = global->last;
if(result == PARAM_NEXT_OPERATION) {

@ -5,7 +5,7 @@
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
* 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
@ -298,6 +298,9 @@ const struct helptxt helptext[] = {
{"-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},

@ -5,7 +5,7 @@
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
* 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
@ -548,11 +548,45 @@ static char *my_useragent(void)
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);

@ -63,7 +63,8 @@ test352 test353 test354 test355 test356 test357 test358 test359 test360 \
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 \

56
tests/data/test383 Normal file

@ -0,0 +1,56 @@
<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
Host: %HOSTIP:%HTTPPORT
User-Agent: curl/%VERSION
Content-Type: application/json
Accept: application/json
Content-Length: 20
{ "drink": "coffe" }
</protocol>
</verify>
</testcase>

59
tests/data/test384 Normal file

@ -0,0 +1,59 @@
<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
Host: %HOSTIP:%HTTPPORT
User-Agent: curl/%VERSION
Accept: foobar/*
Content-Type: application/json
Content-Length: 21
{ "drink": "coffe" }
</protocol>
</verify>
</testcase>

56
tests/data/test385 Normal file

@ -0,0 +1,56 @@
<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
Host: %HOSTIP:%HTTPPORT
User-Agent: curl/%VERSION
Content-Type: drinks/hot
Accept: application/json
Content-Length: 40
{ "drink": "coffe", "crunch": "cookie" }
</protocol>
</verify>
</testcase>

74
tests/data/test386 Normal file

@ -0,0 +1,74 @@
<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
Host: %HOSTIP:%HTTPPORT
User-Agent: curl/%VERSION
Content-Type: application/json
Accept: application/json
Content-Length: 20
{ "drink": "coffe" }GET /%TESTNUMBER0002 HTTP/1.1
Host: %HOSTIP:%HTTPPORT
User-Agent: curl/%VERSION
Accept: */*
</protocol>
</verify>
</testcase>