]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
add syntax parsing for key,value in foreach
authorAlan T. DeKok <aland@freeradius.org>
Thu, 12 Sep 2024 19:58:39 +0000 (15:58 -0400)
committerAlan T. DeKok <aland@freeradius.org>
Fri, 13 Sep 2024 13:39:17 +0000 (09:39 -0400)
foreach string key, integer value (...)

missing:

* key da / variable is not created
* there's no way to turn a tmpl_dcursor into a string

so we will first need to add a tmpl_dcursor_print() to print
the dcursor

src/lib/server/cf_file.c
src/tests/keywords/foreach-key [new file with mode: 0644]

index 01ee69f9883c7eb7bfe282baf12bbdc80eb81879..2679f234a2f5bbd915b744ba2288b65ecb1e9cff 100644 (file)
@@ -1994,89 +1994,173 @@ static int parse_error(cf_stack_t *stack, char const *ptr, char const *message)
        return -1;
 }
 
+static int parse_type_name(cf_stack_t *stack, char const **ptr_p, char const *type_ptr, fr_type_t *type_p)
+{
+       fr_type_t type;
+       fr_token_t token;
+       char const *ptr = *ptr_p;
+       char const *ptr2;
+
+       /*
+        *      Parse an explicit type.
+        */
+       type = fr_table_value_by_str(fr_type_table, stack->buff[1], FR_TYPE_NULL);
+       switch (type) {
+       default:
+               break;
+
+       case FR_TYPE_NULL:
+       case FR_TYPE_VOID:
+       case FR_TYPE_VALUE_BOX:
+       case FR_TYPE_MAX:
+               (void) parse_error(stack, type_ptr, "Unknown or invalid variable type in 'foreach'");
+               return -1;
+       }
+
+       fr_skip_whitespace(ptr);
+       ptr2 = ptr;
+
+       /*
+        *      Parse the variable name.  @todo - allow '-' in names.
+        */
+       token = gettoken(&ptr, stack->buff[2], stack->bufsize, false);
+       if (token != T_BARE_WORD) {
+               (void) parse_error(stack, ptr2, "Invalid variable name for key in 'foreach'");
+               return NULL;
+       }
+       fr_skip_whitespace(ptr);
+
+       *ptr_p = ptr;
+       *type_p = type;
+
+       return 0;
+}
+
+/*
+ *     foreach &User-Name {  - old and deprecated
+ *
+ *     foreach value (...) { - automatically define variable
+ *
+ *     foreach string value ( ...) { - data type for variable
+ *
+ *     foreach string key, type value (..) { - key is "string", value is as above
+ */
 static CONF_ITEM *process_foreach(cf_stack_t *stack)
 {
        fr_token_t      token;
        fr_type_t       type;
        CONF_SECTION    *css;
-       char const      *ptr = stack->ptr, *ptr2;
+       char const      *ptr = stack->ptr, *ptr2, *type_ptr;
        cf_stack_frame_t *frame = &stack->frame[stack->depth];
        CONF_SECTION    *parent = frame->current;
 
+       css = cf_section_alloc(parent, parent, "foreach", NULL);
+       if (!css) {
+               ERROR("%s[%d]: Failed allocating memory for section",
+                     frame->filename, frame->lineno);
+               return NULL;
+       }
+
+       cf_filename_set(css, frame->filename);
+       cf_lineno_set(css, frame->lineno);
+       css->name2_quote = T_BARE_WORD;
+       css->unlang = CF_UNLANG_ALLOW;
+       css->allow_locals = true;
+
        /*
         *      Get the first argument to "foreach".  For backwards
         *      compatibility, it could be an attribute reference.
         */
-       ptr2 = ptr;
+       type_ptr = ptr;
        if (cf_get_token(parent, &ptr, &token, stack->buff[1], stack->bufsize,
                         frame->filename, frame->lineno) < 0) {
                return NULL;
        }
 
        if (token != T_BARE_WORD) {
-               (void) parse_error(stack, ptr2, "Unexpected argument to 'foreach'");
+       invalid_argument:
+               (void) parse_error(stack, type_ptr, "Unexpected argument to 'foreach'");
                return NULL;
        }
 
        fr_skip_whitespace(ptr);
 
+       /*
+        *      foreach foo { ...
+        *
+        *      Deprecated and don't use.
+        */
        if (*ptr == '{') {
-               css = cf_section_alloc(parent, parent, "foreach", stack->buff[1]);
-               if (!css) {
-                       ERROR("%s[%d]: Failed allocating memory for section",
-                             frame->filename, frame->lineno);
-                       return NULL;
-               }
-
-               cf_filename_set(css, frame->filename);
-               cf_lineno_set(css, frame->lineno);
-               css->name2_quote = T_BARE_WORD;
-               css->unlang = CF_UNLANG_ALLOW;
-               css->allow_locals = true;
+               css->name2 = talloc_typed_strdup(css, stack->buff[1]);
 
                ptr++;
                stack->ptr = ptr;
 
+               cf_log_warn(css, "Using deprecated syntax.  Please use new the new 'foreach' syntax.");
                return cf_section_to_item(css);
        }
 
        fr_skip_whitespace(ptr);
 
        /*
-        *
+        *      foreach value (...) {
         */
        if (*ptr == '(') {
                type = FR_TYPE_NULL;
-
-               strcpy(stack->buff[2], stack->buff[1]);
-               goto parse_expression;
+               strcpy(stack->buff[2], stack->buff[1]); /* so that we can parse expression in buff[1] */
+               goto alloc_argc_2;
        }
 
-       type = fr_table_value_by_str(fr_type_table, stack->buff[1], FR_TYPE_NULL);
-       switch (type) {
-       default:
-               break;
-
-       case FR_TYPE_NULL:
-       case FR_TYPE_VOID:
-       case FR_TYPE_VALUE_BOX:
-       case FR_TYPE_MAX:
-               (void) parse_error(stack, ptr2, "Unknown or invalid variable type in 'foreach'");
-               return NULL;
-       }
+       /*
+        *      on input, type name is in stack->buff[1]
+        *      on output, variable name is in stack->buff[2]
+        */
+       if (parse_type_name(stack, &ptr, type_ptr, &type) < 0) return NULL;
 
-       fr_skip_whitespace(ptr);
-       ptr2 = ptr;
+       /*
+        *      if we now have an expression block, then just have variable type / name.
+        */
+       if (*ptr == '(') goto alloc_argc_2;
 
        /*
-        *      Parse the variable name.  @todo - allow '-' in names.
+        *      There's a comma.  the first "type name" is for the key.  We skip the comma, and parse the
+        *      second "type name" as being for the value.
+        *
+        *      foreach type key, type value (...)
         */
-       token = gettoken(&ptr, stack->buff[2], stack->bufsize, false);
-       if (token != T_BARE_WORD) {
-               (void) parse_error(stack, ptr2, "Invalid variable name for key in 'foreach'");
-               return NULL;
+       if (*ptr == ',') {
+               /*
+                *      We have 4 arguments, [var-type, var-name, key-type, key-name]
+                *
+                *      We don't really care about key-type, but we might care later.
+                */
+               css->argc = 4;
+               css->argv = talloc_array(css, char const *, css->argc);
+               css->argv_quote = talloc_array(css, fr_token_t, css->argc);
+
+               css->argv[2] = fr_type_to_str(type);
+               css->argv_quote[2] = T_BARE_WORD;
+
+               css->argv[3] = talloc_typed_strdup(css->argv, stack->buff[2]);
+               css->argv_quote[3] = T_BARE_WORD;
+
+               ptr++;
+               fr_skip_whitespace(ptr);
+               type_ptr = ptr;
+
+               /*
+                *      Now parse "type value"
+                */
+               token = gettoken(&ptr, stack->buff[1], stack->bufsize, false);
+               if (token != T_BARE_WORD) goto invalid_argument;
+
+               if (parse_type_name(stack, &ptr, type_ptr, &type) < 0) return NULL;
+
+               if (!fr_type_is_leaf(type)) {
+                       (void) parse_error(stack, type_ptr, "Invalid data type for 'key' variable");
+                       return NULL;
+               }
        }
-       fr_skip_whitespace(ptr);
 
        /*
         *      The thing to loop over must now be in an expression block.
@@ -2086,10 +2170,18 @@ static CONF_ITEM *process_foreach(cf_stack_t *stack)
                return NULL;
        }
 
+       goto parse_expression;
+
+alloc_argc_2:
+       css->argc = 2;
+       css->argv = talloc_array(css, char const *, css->argc);
+       css->argv_quote = talloc_array(css, fr_token_t, css->argc);
+
+
+parse_expression:
        /*
         *      "(" whitespace EXPRESSION whitespace ")"
         */
-parse_expression:
        ptr++;
        fr_skip_whitespace(ptr);
 
@@ -2098,8 +2190,11 @@ parse_expression:
                return NULL;
        }
 
+       /*
+        *      We can do &foo[*] or %func(...), but not "...".
+        */
        if (token != T_BARE_WORD) {
-               (void) parse_error(stack, ptr2, "Invalid thingy in 'foreach'");
+               (void) parse_error(stack, ptr2, "Invalid reference in 'foreach'");
                return NULL;
        }
 
@@ -2116,26 +2211,11 @@ parse_expression:
                return NULL;
        }
 
-       css = cf_section_alloc(parent, parent, "foreach", stack->buff[1]);
-       if (!css) {
-               ERROR("%s[%d]: Failed allocating memory for section",
-                     frame->filename, frame->lineno);
-               return NULL;
-       }
-
-       cf_filename_set(css, frame->filename);
-       cf_lineno_set(css, frame->lineno);
-       css->name2_quote = T_BARE_WORD;
-       css->unlang = CF_UNLANG_ALLOW;
-       css->allow_locals = true;
+       css->name2 = talloc_typed_strdup(css, stack->buff[1]);
 
        /*
         *      Add in the extra arguments
         */
-       css->argc = 2;
-       css->argv = talloc_array(css, char const *, css->argc);
-       css->argv_quote = talloc_array(css, fr_token_t, css->argc);
-
        css->argv[0] = fr_type_to_str(type);
        css->argv_quote[0] = T_BARE_WORD;
 
diff --git a/src/tests/keywords/foreach-key b/src/tests/keywords/foreach-key
new file mode 100644 (file)
index 0000000..0e97dc6
--- /dev/null
@@ -0,0 +1,20 @@
+group foo
+string total
+
+foo := {User-Name = "a", User-Password = "b", User-Name = "c", User-Password = "d" }
+
+total = ""
+
+#
+#  For now, just test that we can parse key things
+#
+foreach string key, string name (foo.User-Name[*]) {
+       total += name
+       total += ","
+}
+
+if (total != "a,c,") {
+       test_fail
+}
+
+success