commit 0841a69357d73353905f8012f455ec6128201131 Author: Nick Mathewson nickm@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,