]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
tool_getparam: initial --json support
authorDaniel Stenberg <daniel@haxx.se>
Fri, 21 Jan 2022 08:38:44 +0000 (09:38 +0100)
committerDaniel Stenberg <daniel@haxx.se>
Tue, 1 Feb 2022 09:39:55 +0000 (10:39 +0100)
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

12 files changed:
docs/cmdline-opts/Makefile.inc
docs/cmdline-opts/json.d [new file with mode: 0644]
docs/options-in-versions
src/tool_cfgable.h
src/tool_getparam.c
src/tool_listhelp.c
src/tool_paramhlp.c
tests/data/Makefile.inc
tests/data/test383 [new file with mode: 0644]
tests/data/test384 [new file with mode: 0644]
tests/data/test385 [new file with mode: 0644]
tests/data/test386 [new file with mode: 0644]

index f8b5711271c454d2ea1910b85813ab477f2de41e..afa3d7931f4263beefa43213b0a80d0ca619e66c 100644 (file)
@@ -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 \
diff --git a/docs/cmdline-opts/json.d b/docs/cmdline-opts/json.d
new file mode 100644 (file)
index 0000000..3bdb0a7
--- /dev/null
@@ -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.
index e75df6e06013ab7b16de4d64ceea4fe52f85973a..5d242b8ffc5c7a62620b454ce0e9c7af40bdcebd 100644 (file)
@@ -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
index 227b914e3379040a3ef3e9e643f0c478fb22d406..a06ef602819b5f26ae0cc82f8bf993aee0fdfaae 100644 (file)
@@ -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;
index 14dca833cd0e21b4ef4cacfc91901d27f7c31e99..9bf1c594cf0257d2e2417ec7d6627ca7ca80681c 100644 (file)
@@ -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) {
index 448fc7cb30f2fd2b07933fa722173e937410dfb5..3bca52c0e69737cb97f05cf61ebeca1a1b231c72 100644 (file)
@@ -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},
index 8ac6cf53e04cdca55a9e931648810fa2218658d2..273805ef0d1769b51de79c4f18e26f4ec086cd37 100644 (file)
@@ -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);
index d0f2a7180603dce4f8262848401c6340fa6df6eb..59d46bc40bd0561d2460813475634909361e514f 100644 (file)
@@ -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 \
diff --git a/tests/data/test383 b/tests/data/test383
new file mode 100644 (file)
index 0000000..6ba8f5e
--- /dev/null
@@ -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\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>
diff --git a/tests/data/test384 b/tests/data/test384
new file mode 100644 (file)
index 0000000..9651f9f
--- /dev/null
@@ -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\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>
diff --git a/tests/data/test385 b/tests/data/test385
new file mode 100644 (file)
index 0000000..ee543fa
--- /dev/null
@@ -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\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>
diff --git a/tests/data/test386 b/tests/data/test386
new file mode 100644 (file)
index 0000000..016dc46
--- /dev/null
@@ -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\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>