Skip to content

Commit 32160ca

Browse files
committed
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 #8314
1 parent 1ce1f0b commit 32160ca

12 files changed

+343
-12
lines changed

docs/cmdline-opts/Makefile.inc

+2-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
# | (__| |_| | _ <| |___
66
# \___|\___/|_| \_\_____|
77
#
8-
# Copyright (C) 1998 - 2021, Daniel Stenberg, <daniel@haxx.se>, et al.
8+
# Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
99
#
1010
# This software is licensed as described in the file COPYING, which
1111
# you should have received as part of this distribution. The terms
@@ -111,6 +111,7 @@ DPAGES = \
111111
interface.d \
112112
ipv4.d \
113113
ipv6.d \
114+
json.d \
114115
junk-session-cookies.d \
115116
keepalive-time.d \
116117
key-type.d \

docs/cmdline-opts/json.d

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
Long: json
2+
Arg: <data>
3+
Help: HTTP POST JSON
4+
Protocols: HTTP
5+
See-also: data-binary data-raw
6+
Mutexed: form head upload-file
7+
Category: http post upload
8+
Example: --json '{ "drink": "coffe" }' $URL
9+
Example: --json '{ "drink":' --json ' "coffe" }' $URL
10+
Example: --json @prepared $URL
11+
Example: --json @- $URL < json.txt
12+
Added: 7.82.0
13+
---
14+
Sends the specified JSON data in a POST request to the HTTP server. --json
15+
works as a shortcut for passing on these three options:
16+
17+
--data [arg]
18+
--header "Content-Type: application/json"
19+
--header "Accept: application/json"
20+
21+
There is **no verification** that the passed in data is actual JSON or that
22+
the syntax is correct.
23+
24+
If you start the data with the letter @, the rest should be a file name to
25+
read the data from, or a single dash (-) if you want curl to read the data
26+
from stdin. Posting data from a file named \&'foobar' would thus be done with
27+
--json @foobar and to instead read the data from stdin, use --json @-.
28+
29+
If this option is used more than once on the same command line, the additional
30+
data pieces will be concatenated to the previous before sending.
31+
32+
The headers this option sets can be overriden with --header as usual.

docs/options-in-versions

+1
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@
9999
--interface 7.3
100100
--ipv4 (-4) 7.10.8
101101
--ipv6 (-6) 7.10.8
102+
--json 7.82.0
102103
--junk-session-cookies (-j) 7.9.7
103104
--keepalive-time 7.18.0
104105
--key 7.9.3

src/tool_cfgable.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* | (__| |_| | _ <| |___
88
* \___|\___/|_| \_\_____|
99
*
10-
* Copyright (C) 1998 - 2021, Daniel Stenberg, <daniel@haxx.se>, et al.
10+
* Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
1111
*
1212
* This software is licensed as described in the file COPYING, which
1313
* you should have received as part of this distribution. The terms
@@ -189,6 +189,7 @@ struct OperationConfig {
189189
bool proxydigest;
190190
bool proxybasic;
191191
bool proxyanyauth;
192+
bool jsoned; /* added json content-type */
192193
char *writeout; /* %-styled format string to output */
193194
struct curl_slist *quote;
194195
struct curl_slist *postquote;

src/tool_getparam.c

+20-7
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,7 @@ static const struct LongShort aliases[]= {
230230
{"da", "data-ascii", ARG_STRING},
231231
{"db", "data-binary", ARG_STRING},
232232
{"de", "data-urlencode", ARG_STRING},
233+
{"df", "json", ARG_STRING},
233234
{"D", "dump-header", ARG_FILENAME},
234235
{"e", "referer", ARG_STRING},
235236
{"E", "cert", ARG_FILENAME},
@@ -1386,7 +1387,7 @@ ParameterError getparameter(const char *flag, /* f or -long-flag */
13861387
size_t size = 0;
13871388
bool raw_mode = (subletter == 'r');
13881389

1389-
if(subletter == 'e') { /* --data-urlencode*/
1390+
if(subletter == 'e') { /* --data-urlencode */
13901391
/* [name]=[content], we encode the content part only
13911392
* [name]@[file name]
13921393
*
@@ -1489,7 +1490,8 @@ ParameterError getparameter(const char *flag, /* f or -long-flag */
14891490
"an empty POST.\n", nextarg);
14901491
}
14911492

1492-
if(subletter == 'b')
1493+
if((subletter == 'b') || /* --data-binary */
1494+
(subletter == 'f') /* --json */)
14931495
/* forced binary */
14941496
err = file2memory(&postdata, &size, file);
14951497
else {
@@ -1516,6 +1518,8 @@ ParameterError getparameter(const char *flag, /* f or -long-flag */
15161518
if(postdata)
15171519
size = strlen(postdata);
15181520
}
1521+
if(subletter == 'f')
1522+
config->jsoned = TRUE;
15191523

15201524
#ifdef CURL_DOES_CONVERSIONS
15211525
if(subletter != 'b') {
@@ -1540,13 +1544,21 @@ ParameterError getparameter(const char *flag, /* f or -long-flag */
15401544
return PARAM_NO_MEM;
15411545
}
15421546
memcpy(config->postfields, oldpost, (size_t)oldlen);
1543-
/* use byte value 0x26 for '&' to accommodate non-ASCII platforms */
1544-
config->postfields[oldlen] = '\x26';
1545-
memcpy(&config->postfields[oldlen + 1], postdata, size);
1546-
config->postfields[oldlen + 1 + size] = '\0';
1547+
if(subletter != 'f') {
1548+
/* skip this treatment for --json */
1549+
/* use byte value 0x26 for '&' to accommodate non-ASCII platforms */
1550+
config->postfields[oldlen] = '\x26';
1551+
memcpy(&config->postfields[oldlen + 1], postdata, size);
1552+
config->postfields[oldlen + 1 + size] = '\0';
1553+
config->postfieldsize += size + 1;
1554+
}
1555+
else {
1556+
memcpy(&config->postfields[oldlen], postdata, size);
1557+
config->postfields[oldlen + size] = '\0';
1558+
config->postfieldsize += size;
1559+
}
15471560
Curl_safefree(oldpost);
15481561
Curl_safefree(postdata);
1549-
config->postfieldsize += size + 1;
15501562
}
15511563
else {
15521564
config->postfields = postdata;
@@ -2367,6 +2379,7 @@ ParameterError parse_args(struct GlobalConfig *global, int argc,
23672379
: NULL;
23682380

23692381
result = getparameter(orig_opt, nextarg, &passarg, global, config);
2382+
23702383
curlx_unicodefree(nextarg);
23712384
config = global->last;
23722385
if(result == PARAM_NEXT_OPERATION) {

src/tool_listhelp.c

+4-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
* | (__| |_| | _ <| |___
66
* \___|\___/|_| \_\_____|
77
*
8-
* Copyright (C) 1998 - 2021, Daniel Stenberg, <daniel.se>, et al.
8+
* Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel.se>, et al.
99
*
1010
* This software is licensed as described in the file COPYING, which
1111
* you should have received as part of this distribution. The terms
@@ -298,6 +298,9 @@ const struct helptxt helptext[] = {
298298
{"-6, --ipv6",
299299
"Resolve names to IPv6 addresses",
300300
CURLHELP_CONNECTION | CURLHELP_DNS},
301+
{" --json <data>",
302+
"HTTP POST JSON",
303+
CURLHELP_HTTP | CURLHELP_POST | CURLHELP_UPLOAD},
301304
{"-j, --junk-session-cookies",
302305
"Ignore session cookies read from file",
303306
CURLHELP_HTTP},

src/tool_paramhlp.c

+35-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
* | (__| |_| | _ <| |___
66
* \___|\___/|_| \_\_____|
77
*
8-
* Copyright (C) 1998 - 2021, Daniel Stenberg, <daniel@haxx.se>, et al.
8+
* Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
99
*
1010
* This software is licensed as described in the file COPYING, which
1111
* you should have received as part of this distribution. The terms
@@ -548,11 +548,45 @@ static char *my_useragent(void)
548548
return strdup(CURL_NAME "/" CURL_VERSION);
549549
}
550550

551+
#define isheadersep(x) ((((x)==':') || ((x)==';')))
552+
553+
/*
554+
* inlist() returns true if the given 'checkfor' header is present in the
555+
* header list.
556+
*/
557+
static bool inlist(const struct curl_slist *head,
558+
const char *checkfor)
559+
{
560+
size_t thislen = strlen(checkfor);
561+
DEBUGASSERT(thislen);
562+
DEBUGASSERT(checkfor[thislen-1] != ':');
563+
564+
for(; head; head = head->next) {
565+
if(curl_strnequal(head->data, checkfor, thislen) &&
566+
isheadersep(head->data[thislen]) )
567+
return TRUE;
568+
}
569+
570+
return FALSE;
571+
}
572+
551573
CURLcode get_args(struct OperationConfig *config, const size_t i)
552574
{
553575
CURLcode result = CURLE_OK;
554576
bool last = (config->next ? FALSE : TRUE);
555577

578+
if(config->jsoned) {
579+
ParameterError err = PARAM_OK;
580+
/* --json also implies json Content-Type: and Accept: headers - if
581+
they are not set with -H */
582+
if(!inlist(config->headers, "Content-Type"))
583+
err = add2list(&config->headers, "Content-Type: application/json");
584+
if(!err && !inlist(config->headers, "Accept"))
585+
err = add2list(&config->headers, "Accept: application/json");
586+
if(err)
587+
return CURLE_OUT_OF_MEMORY;
588+
}
589+
556590
/* Check we have a password for the given host user */
557591
if(config->userpwd && !config->oauth_bearer) {
558592
result = checkpasswd("host", i, last, &config->userpwd);

tests/data/Makefile.inc

+2-1
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,8 @@ test352 test353 test354 test355 test356 test357 test358 test359 test360 \
6363
test361 test362 test363 test364 test365 test366 test367 test368 test369 \
6464
test370 test371 test372 test373 test374 \
6565
\
66-
test380 test381 \
66+
test380 test381 test383 test384 test385 test386 \
67+
\
6768
test392 test393 test394 test395 test396 test397 \
6869
\
6970
test400 test401 test402 test403 test404 test405 test406 test407 test408 \

tests/data/test383

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<testcase>
2+
<info>
3+
<keywords>
4+
HTTP
5+
HTTP POST
6+
--json
7+
</keywords>
8+
</info>
9+
#
10+
# Server-side
11+
<reply>
12+
<data>
13+
HTTP/1.1 200 OK
14+
Date: Tue, 09 Nov 2010 14:49:00 GMT
15+
Server: test-server/fake
16+
Last-Modified: Tue, 13 Jun 2000 12:10:00 GMT
17+
ETag: "21025-dc7-39462498"
18+
Accept-Ranges: bytes
19+
Content-Length: 6
20+
Connection: close
21+
Content-Type: text/html
22+
Funny-head: yesyes
23+
24+
-foo-
25+
</data>
26+
</reply>
27+
28+
#
29+
# Client-side
30+
<client>
31+
<server>
32+
http
33+
</server>
34+
<name>
35+
HTTP with --json
36+
</name>
37+
<command>
38+
--json '{ "drink": "coffe" }' http://%HOSTIP:%HTTPPORT/%TESTNUMBER
39+
</command>
40+
</client>
41+
42+
#
43+
# Verify data after the test has been "shot"
44+
<verify>
45+
<protocol nonewline="yes">
46+
POST /%TESTNUMBER HTTP/1.1
47+
Host: %HOSTIP:%HTTPPORT
48+
User-Agent: curl/%VERSION
49+
Content-Type: application/json
50+
Accept: application/json
51+
Content-Length: 20
52+
53+
{ "drink": "coffe" }
54+
</protocol>
55+
</verify>
56+
</testcase>

tests/data/test384

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<testcase>
2+
<info>
3+
<keywords>
4+
HTTP
5+
HTTP POST
6+
--json
7+
</keywords>
8+
</info>
9+
#
10+
# Server-side
11+
<reply>
12+
<data>
13+
HTTP/1.1 200 OK
14+
Date: Tue, 09 Nov 2010 14:49:00 GMT
15+
Server: test-server/fake
16+
Last-Modified: Tue, 13 Jun 2000 12:10:00 GMT
17+
ETag: "21025-dc7-39462498"
18+
Accept-Ranges: bytes
19+
Content-Length: 6
20+
Connection: close
21+
Content-Type: text/html
22+
Funny-head: yesyes
23+
24+
-foo-
25+
</data>
26+
</reply>
27+
28+
#
29+
# Client-side
30+
<client>
31+
<server>
32+
http
33+
</server>
34+
<name>
35+
HTTP with --json from stdin
36+
</name>
37+
<stdin>
38+
{ "drink": "coffe" }
39+
</stdin>
40+
<command>
41+
--json @- http://%HOSTIP:%HTTPPORT/%TESTNUMBER -H "Accept: foobar/*"
42+
</command>
43+
</client>
44+
45+
#
46+
# Verify data after the test has been "shot"
47+
<verify>
48+
<protocol>
49+
POST /%TESTNUMBER HTTP/1.1
50+
Host: %HOSTIP:%HTTPPORT
51+
User-Agent: curl/%VERSION
52+
Accept: foobar/*
53+
Content-Type: application/json
54+
Content-Length: 21
55+
56+
{ "drink": "coffe" }
57+
</protocol>
58+
</verify>
59+
</testcase>

0 commit comments

Comments
 (0)