[tor-commits] [tor/master] Allow kvlines in control commands.

dgoulet at torproject.org dgoulet at torproject.org
Tue Apr 30 15:57:51 UTC 2019


commit 0841a69357d73353905f8012f455ec6128201131
Author: Nick Mathewson <nickm at torproject.org>
Date:   Fri Apr 5 15:29:56 2019 -0400

    Allow kvlines in control commands.
---
 src/feature/control/control_cmd.c         | 46 +++++++++++++++++++++++++++++--
 src/feature/control/control_cmd.h         | 17 ++++++++++++
 src/feature/control/control_cmd_args_st.h |  4 +++
 src/test/test_controller.c                | 24 ++++++++++++++++
 4 files changed, 89 insertions(+), 2 deletions(-)

diff --git a/src/feature/control/control_cmd.c b/src/feature/control/control_cmd.c
index f457e9fa5..727950a93 100644
--- a/src/feature/control/control_cmd.c
+++ b/src/feature/control/control_cmd.c
@@ -40,6 +40,7 @@
 #include "lib/crypt_ops/crypto_rand.h"
 #include "lib/crypt_ops/crypto_util.h"
 #include "lib/encoding/confline.h"
+#include "lib/encoding/kvline.h"
 
 #include "core/or/cpath_build_state_st.h"
 #include "core/or/entry_connection_st.h"
@@ -74,12 +75,28 @@ control_cmd_args_free_(control_cmd_args_t *args)
     SMARTLIST_FOREACH(args->args, char *, c, tor_free(c));
     smartlist_free(args->args);
   }
+  config_free_lines(args->kwargs);
   tor_free(args->object);
 
   tor_free(args);
 }
 
 /**
+ * Return true iff any element of the NULL-terminated <b>array</b> matches
+ * <b>kwd</b>. Case-insensitive.
+ **/
+static bool
+string_array_contains_keyword(const char **array, const char *kwd)
+{
+  for (unsigned i = 0; array[i]; ++i) {
+    if (! strcasecmp(array[i], kwd))
+      return true;
+  }
+  return false;
+}
+
+
+/**
  * Helper: parse the arguments to a command according to <b>syntax</b>.  On
  * success, set *<b>error_out</b> to NULL and return a newly allocated
  * control_cmd_args_t.  On failure, set *<b>error_out</b> to newly allocated
@@ -96,6 +113,7 @@ control_cmd_parse_args(const char *command,
   control_cmd_args_t *result = tor_malloc_zero(sizeof(control_cmd_args_t));
   const char *cmdline;
   char *cmdline_alloc = NULL;
+  tor_assert(syntax->max_args < INT_MAX || syntax->max_args == UINT_MAX);
 
   result->command = command;
 
@@ -120,18 +138,42 @@ control_cmd_parse_args(const char *command,
 
   result->args = smartlist_new();
   smartlist_split_string(result->args, cmdline, " ",
-                         SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0);
+                         SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK,
+                         (int)(syntax->max_args+1));
   size_t n_args = smartlist_len(result->args);
   if (n_args < syntax->min_args) {
     tor_asprintf(error_out, "Need at least %u argument(s)",
                  syntax->min_args);
     goto err;
-  } else if (n_args > syntax->max_args) {
+  } else if (n_args > syntax->max_args && ! syntax->accept_keywords) {
     tor_asprintf(error_out, "Cannot accept more than %u argument(s)",
                  syntax->max_args);
     goto err;
   }
 
+  if (n_args > syntax->max_args) {
+    tor_assert(n_args == syntax->max_args + 1);
+    tor_assert(syntax->accept_keywords);
+    char *remainder = smartlist_pop_last(result->args);
+    result->kwargs = kvline_parse(remainder, syntax->kvline_flags);
+    tor_free(remainder);
+    if (result->kwargs == NULL) {
+      tor_asprintf(error_out, "Cannot parse keyword argument(s)");
+      goto err;
+    }
+    if (syntax->allowed_keywords) {
+      /* Check for unpermitted arguments */
+      const config_line_t *line;
+      for (line = result->kwargs; line; line = line->next) {
+        if (! string_array_contains_keyword(syntax->allowed_keywords,
+                                            line->key)) {
+          tor_asprintf(error_out, "Unrecognized keyword argument %s",
+                       escaped(line->key));
+        }
+      }
+    }
+  }
+
   tor_assert_nonfatal(*error_out == NULL);
   goto done;
  err:
diff --git a/src/feature/control/control_cmd.h b/src/feature/control/control_cmd.h
index 801bf4370..6f35c74de 100644
--- a/src/feature/control/control_cmd.h
+++ b/src/feature/control/control_cmd.h
@@ -44,6 +44,23 @@ typedef struct control_cmd_syntax_t {
    **/
   unsigned int max_args;
   /**
+   * If true, we should parse options after the positional arguments
+   * as a set of unordered flags and key=value arguments.
+   *
+   * Requires that max_args is not UINT_MAX.
+   **/
+  bool accept_keywords;
+  /**
+   * If accept_keywords is true, then only the keywords listed in this
+   * (NULL-terminated) array are valid keywords for this command.
+   **/
+  const char **allowed_keywords;
+  /**
+   * If accept_keywords is true, this option is passed to kvline_parse() as
+   * its flags.
+   **/
+  unsigned kvline_flags;
+  /**
    * True iff this command wants to be followed by a multiline object.
    **/
   bool want_object;
diff --git a/src/feature/control/control_cmd_args_st.h b/src/feature/control/control_cmd_args_st.h
index 9ecc5d750..06e2a183b 100644
--- a/src/feature/control/control_cmd_args_st.h
+++ b/src/feature/control/control_cmd_args_st.h
@@ -32,6 +32,10 @@ struct control_cmd_args_t {
    **/
   struct smartlist_t *args;
   /**
+   * Keyword arguments to the command.
+   **/
+  struct config_line_t *kwargs;
+  /**
    * Number of bytes in <b>object</b>; 0 if <b>object</b> is not set.
    **/
   size_t object_len;
diff --git a/src/test/test_controller.c b/src/test/test_controller.c
index ca853367a..dc286dacc 100644
--- a/src/test/test_controller.c
+++ b/src/test/test_controller.c
@@ -18,6 +18,8 @@
 #include "test/test.h"
 #include "test/test_helpers.h"
 #include "lib/net/resolve.h"
+#include "lib/encoding/confline.h"
+#include "lib/encoding/kvline.h"
 
 #include "feature/control/control_connection_st.h"
 #include "feature/control/control_cmd_args_st.h"
@@ -58,6 +60,16 @@ control_cmd_dump_args(const control_cmd_args_t *result)
     buf_add_string(buf, ", obj=");
     buf_add_string(buf, escaped(result->object));
   }
+  if (result->kwargs) {
+    buf_add_string(buf, ", { ");
+    const config_line_t *line;
+    for (line = result->kwargs; line; line = line->next) {
+      const bool last = (line->next == NULL);
+      buf_add_printf(buf, "%s=%s%s ", line->key, escaped(line->value),
+                     last ? "" : ",");
+    }
+    buf_add_string(buf, "}");
+  }
   buf_add_string(buf, " }");
 
   char *encoded = buf_extract(buf, NULL);
@@ -152,6 +164,17 @@ static const control_cmd_syntax_t no_args_one_obj_syntax = {
 static const parse_test_params_t parse_no_args_one_obj_params =
   TESTPARAMS( no_args_one_obj_syntax, no_args_one_obj_tests );
 
+static const parser_testcase_t no_args_kwargs_tests[] = {
+  OK("", "{ args=[] }"),
+};
+static const control_cmd_syntax_t no_args_kwargs_syntax = {
+   .min_args=0, .max_args=0,
+   .accept_keywords=true,
+   .kvline_flags=KV_OMIT_VALS
+};
+static const parse_test_params_t parse_no_args_kwargs_params =
+  TESTPARAMS( no_args_kwargs_syntax, no_args_kwargs_tests );
+
 static void
 test_add_onion_helper_keyarg_v3(void *arg)
 {
@@ -1752,6 +1775,7 @@ test_getinfo_md_all(void *arg)
 struct testcase_t controller_tests[] = {
   PARSER_TEST(one_to_three),
   PARSER_TEST(no_args_one_obj),
+  PARSER_TEST(no_args_kwargs),
   { "add_onion_helper_keyarg_v2", test_add_onion_helper_keyarg_v2, 0,
     NULL, NULL },
   { "add_onion_helper_keyarg_v3", test_add_onion_helper_keyarg_v3, 0,





More information about the tor-commits mailing list