]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MEDIUM: acl/pattern: standardisation "of pat_parse_int()" and "pat_parse_dotted_ver()"
authorThierry FOURNIER <tfournier@exceliance.fr>
Thu, 23 Jan 2014 16:40:34 +0000 (17:40 +0100)
committerWilly Tarreau <w@1wt.eu>
Mon, 17 Mar 2014 17:06:06 +0000 (18:06 +0100)
The goal of these patch is to simplify the prototype of
"pat_pattern_*()" functions. I want to replace the argument "char
**args" by a simple "char *arg" and remove the "opaque" argument.

"pat_parse_int()" and "pat_parse_dotted_ver()" are the unique pattern
parser using the "opaque" argument and using more than one string
argument of the char **args. These specificities are only used with ACL.
Other systems using this pattern parser (MAP and CLI) just use one
string for describing a range.

This two functions can read a range, but the min and the max must y
specified. This patch extends the syntax to describe a range with
implicit min and max. This is used for operators like "lt", "le", "gt",
and "ge". the syntax is the following:

   ":x" -> no min to "x"
   "x:" -> "x" to no max

This patch moves the parsing of the comparison operator from the
functions "pat_parse_int()" and "pat_parse_dotted_ver()" to the acl
parser. The acl parser read the operator and the values and build a
volatile string readable by the functions "pat_parse_int()" and
"pat_parse_dotted_ver()". The transformation is done with these rules:

If the parser is "pat_parse_int()":

   "eq x" -> "x"
   "le x" -> ":x"
   "lt x" -> ":y" (with y = x - 1)
   "ge x" -> "x:"
   "gt x" -> "y:" (with y = x + 1)

If the parser is "pat_parse_dotted_ver()":

   "eq x.y" -> "x.y"
   "le x.y" -> ":x.y"
   "lt x.y" -> ":w.z" (with w.z = x.y - 1)
   "ge x.y" -> "x.y:"
   "gt x.y" -> "w.z:" (with w.z = x.y + 1)

Note that, if "y" is not present, assume that is "0".

Now "pat_parse_int()" and "pat_parse_dotted_ver()" accept only one
pattern and the variable "opaque" is no longer used. The prototype of
the pattern parsers can be changed.

include/common/standard.h
src/acl.c
src/pattern.c
src/standard.c

index bdb4ef571a80cc3af9204c659b136cf695827852..cd422cd1462e25394f34146fc799fd570027f7f9 100644 (file)
@@ -42,6 +42,9 @@
 # define ULLONG_MAX    (LLONG_MAX * 2ULL + 1)
 #endif
 
+/* size used for max length of decimal representation of long long int. */
+#define NB_LLMAX_STR (sizeof("-9223372036854775807")-1)
+
 /* number of itoa_str entries */
 #define NB_ITOA_STR    10
 
@@ -388,6 +391,7 @@ extern unsigned int strl2uic(const char *s, int len);
 extern int strl2ic(const char *s, int len);
 extern int strl2irc(const char *s, int len, int *ret);
 extern int strl2llrc(const char *s, int len, long long *ret);
+extern int strl2llrc_dotted(const char *text, int len, long long *ret);
 extern unsigned int read_uint(const char **s, const char *end);
 unsigned int inetaddr_host(const char *text);
 unsigned int inetaddr_host_lim(const char *text, const char *stop);
index 8ce49a91b798562f04363e667a632c161ca6742b..1ffc5728f3f8f6b6d303af845ae02a29d5e3cb3a 100644 (file)
--- a/src/acl.c
+++ b/src/acl.c
@@ -145,6 +145,14 @@ struct acl_expr *parse_acl_expr(const char **args, char **err, struct arg_list *
        unsigned long prev_type;
        int cur_type;
        int nbargs;
+       int operator = STD_OP_EQ;
+       int op;
+       int contain_colon, have_dot;
+       const char *dot;
+       signed long long value, minor;
+       /* The following buffer contain two numbers, a ':' separator and the final \0. */
+       char buffer[NB_LLMAX_STR + 1 + NB_LLMAX_STR + 1];
+       const char *text[2];
 
        /* First, we look for an ACL keyword. And if we don't find one, then
         * we look for a sample fetch expression starting with a sample fetch
@@ -441,8 +449,133 @@ struct acl_expr *parse_acl_expr(const char **args, char **err, struct arg_list *
 
        /* now parse all patterns */
        pattern = NULL;
-       if (!pattern_register(&expr->pat, args, NULL, &pattern, patflags, err))
-               goto out_free_pattern;
+       while (**args) {
+               arg = *args;
+
+               /* Compatibility layer. Each pattern can parse only one string per pattern,
+                * but the pat_parser_int() and pat_parse_dotted_ver() parsers were need
+                * optionnaly two operators. The first operator is the match method: eq,
+                * le, lt, ge and gt. pat_parse_int() and pat_parse_dotted_ver() functions
+                * can have a compatibility syntax based on ranges:
+                *
+                * pat_parse_int():
+                *
+                *   "eq x" -> "x" or "x:x"
+                *   "le x" -> ":x"
+                *   "lt x" -> ":y" (with y = x - 1)
+                *   "ge x" -> "x:"
+                *   "gt x" -> "y:" (with y = x + 1)
+                *
+                * pat_parse_dotted_ver():
+                *
+                *   "eq x.y" -> "x.y" or "x.y:x.y"
+                *   "le x.y" -> ":x.y"
+                *   "lt x.y" -> ":w.z" (with w.z = x.y - 1)
+                *   "ge x.y" -> "x.y:"
+                *   "gt x.y" -> "w.z:" (with w.z = x.y + 1)
+                *
+                * If y is not present, assume that is "0".
+                *
+                * The syntax eq, le, lt, ge and gt are proper to the acl syntax. The
+                * following block of code detect the operator, and rewrite each value
+                * in parsable string.
+                */
+               if (expr->pat.parse == pat_parse_int ||
+                   expr->pat.parse == pat_parse_dotted_ver) {
+                       /* Check for operator. If the argument is operator, memorise it and
+                        * continue to the next argument.
+                        */
+                       op = get_std_op(arg);
+                       if (op != -1) {
+                               operator = op;
+                               args++;
+                               continue;
+                       }
+
+                       /* Check if the pattern contain ':' or '-' character. */
+                       contain_colon = (strchr(arg, ':') || strchr(arg, '-'));
+
+                       /* If the pattern contain ':' or '-' character, give it to the parser as is.
+                        * If no contain ':' and operator is STD_OP_EQ, give it to the parser as is.
+                        * In other case, try to convert the value according with the operator.
+                        */
+                       if (!contain_colon && operator != STD_OP_EQ) {
+                               /* Search '.' separator. */
+                               dot = strchr(arg, '.');
+                               if (!dot) {
+                                       have_dot = 0;
+                                       minor = 0;
+                                       dot = arg + strlen(arg);
+                               }
+                               else
+                                       have_dot = 1;
+
+                               /* convert the integer minor part for the pat_parse_dotted_ver() function. */
+                               if (expr->pat.parse == pat_parse_dotted_ver && have_dot) {
+                                       if (strl2llrc(dot+1, strlen(dot+1), &minor) != 0) {
+                                               memprintf(err, "'%s' is neither a number nor a supported operator", arg);
+                                               goto out_free_pattern;
+                                       }
+                                       if (minor >= 65536) {
+                                               memprintf(err, "'%s' contains too large a minor value", arg);
+                                               goto out_free_pattern;
+                                       }
+                               }
+
+                               /* convert the integer value for the pat_parse_int() function, and the
+                                * integer major part for the pat_parse_dotted_ver() function.
+                                */
+                               if (strl2llrc(arg, dot - arg, &value) != 0) {
+                                       memprintf(err, "'%s' is neither a number nor a supported operator", arg);
+                                       goto out_free_pattern;
+                               }
+                               if (expr->pat.parse == pat_parse_dotted_ver)  {
+                                       if (value >= 65536) {
+                                               memprintf(err, "'%s' contains too large a major value", arg);
+                                               goto out_free_pattern;
+                                       }
+                                       value = (value << 16) | (minor & 0xffff);
+                               }
+
+                               switch (operator) {
+
+                               case STD_OP_EQ: /* this case is not possible. */
+                                       memprintf(err, "internal error");
+                                       goto out_free_pattern;
+
+                               case STD_OP_GT:
+                                       value++; /* gt = ge + 1 */
+
+                               case STD_OP_GE:
+                                       if (expr->pat.parse == pat_parse_int)
+                                               snprintf(buffer, NB_LLMAX_STR+NB_LLMAX_STR+2, "%lld:", value);
+                                       else
+                                               snprintf(buffer, NB_LLMAX_STR+NB_LLMAX_STR+2, "%lld.%lld:",
+                                                        value >> 16, value & 0xffff);
+                                       arg = buffer;
+                                       break;
+
+                               case STD_OP_LT:
+                                       value--; /* lt = le - 1 */
+
+                               case STD_OP_LE:
+                                       if (expr->pat.parse == pat_parse_int)
+                                               snprintf(buffer, NB_LLMAX_STR+NB_LLMAX_STR+2, ":%lld", value);
+                                       else
+                                               snprintf(buffer, NB_LLMAX_STR+NB_LLMAX_STR+2, ":%lld.%lld",
+                                                        value >> 16, value & 0xffff);
+                                       arg = buffer;
+                                       break;
+                               }
+                       }
+               }
+
+               text[0] = arg;
+               text[1] = "";
+               if (!pattern_register(&expr->pat, text, NULL, &pattern, patflags, err))
+                       goto out_free_pattern;
+               args++;
+       }
 
        return expr;
 
index 31df937b57e2b4ac785e963561ae60b19a012c9e..b6816931607ff689e5a57249fbad1944b14ccbe6 100644 (file)
@@ -292,80 +292,74 @@ int pat_parse_reg(const char **text, struct pattern *pattern, enum pat_usage usa
  * The operator is stored in the 'opaque' argument.
  *
  * If err is non-NULL, an error message will be returned there on errors and
- * the caller will have to free it.
+ * the caller will have to free it. The function returns zero on error, and
+ * non-zero on success.
  *
  */
 int pat_parse_int(const char **text, struct pattern *pattern, enum pat_usage usage, int *opaque, char **err)
 {
-       signed long long i;
-       unsigned int j, last, skip = 0;
        const char *ptr = *text;
 
        pattern->type = SMP_T_UINT;
        pattern->expect_type = SMP_T_UINT;
 
-       while (!isdigit((unsigned char)*ptr)) {
-               switch (get_std_op(ptr)) {
-               case STD_OP_EQ: *opaque = 0; break;
-               case STD_OP_GT: *opaque = 1; break;
-               case STD_OP_GE: *opaque = 2; break;
-               case STD_OP_LT: *opaque = 3; break;
-               case STD_OP_LE: *opaque = 4; break;
-               default:
-                       memprintf(err, "'%s' is neither a number nor a supported operator", ptr);
-                       return 0;
-               }
+       /* Empty string is not valid */
+       if (!**text)
+               goto not_valid_range;
 
-               skip++;
-               ptr = text[skip];
-       }
+       /* Search ':' or '-' separator. */
+       while (*ptr != '\0' && *ptr != ':' && *ptr != '-')
+               ptr++;
 
-       last = i = 0;
-       while (1) {
-                j = *ptr++;
-               if ((j == '-' || j == ':') && !last) {
-                       last++;
-                       pattern->val.range.min = i;
-                       i = 0;
-                       continue;
+       /* If separator not found. */
+       if (!*ptr) {
+               if (strl2llrc(*text, ptr - *text, &pattern->val.range.min) != 0) {
+                       memprintf(err, "'%s' is not a number", *text);
+                       return 0;
                }
-               j -= '0';
-                if (j > 9)
-                       // also catches the terminating zero
-                        break;
-                i *= 10;
-                i += j;
-        }
-
-       if (last && *opaque >= 1 && *opaque <= 4) {
-               /* having a range with a min or a max is absurd */
-               memprintf(err, "integer range '%s' specified with a comparison operator", text[skip]);
-               return 0;
+               pattern->val.range.max = pattern->val.range.min;
+               pattern->val.range.min_set = 1;
+               pattern->val.range.max_set = 1;
+               return 1;
        }
 
-       if (!last)
-               pattern->val.range.min = i;
-       pattern->val.range.max = i;
+       /* If the separator is the first character. */
+       if (ptr == *text && *(ptr + 1) != '\0') {
+               if (strl2llrc(ptr + 1, strlen(ptr + 1), &pattern->val.range.max) != 0)
+                       goto not_valid_range;
 
-       switch (*opaque) {
-       case 0: /* eq */
-               pattern->val.range.min_set = 1;
+               pattern->val.range.min_set = 0;
                pattern->val.range.max_set = 1;
-               break;
-       case 1: /* gt */
-               pattern->val.range.min++; /* gt = ge + 1 */
-       case 2: /* ge */
+               return 1;
+       }
+
+       /* If separator is the last character. */
+       if (*(ptr + 1) == '\0') {
+               if (strl2llrc(*text, ptr - *text, &pattern->val.range.min) != 0)
+                       goto not_valid_range;
+
                pattern->val.range.min_set = 1;
                pattern->val.range.max_set = 0;
-               break;
-       case 3: /* lt */
-               pattern->val.range.max--; /* lt = le - 1 */
-       case 4: /* le */
-               pattern->val.range.min_set = 0;
-               pattern->val.range.max_set = 1;
-               break;
+               return 1;
        }
-       return skip + 1;
+
+       /* Else, parse two numbers. */
+       if (strl2llrc(*text, ptr - *text, &pattern->val.range.min) != 0)
+               goto not_valid_range;
+
+       if (strl2llrc(ptr + 1, strlen(ptr + 1), &pattern->val.range.max) != 0)
+               goto not_valid_range;
+
+       if (pattern->val.range.min > pattern->val.range.max)
+               goto not_valid_range;
+
+       pattern->val.range.min_set = 1;
+       pattern->val.range.max_set = 1;
+       return 1;
+
+ not_valid_range:
+       memprintf(err, "'%s' is not a valid number range", *text);
+       return 0;
 }
 
 int pat_parse_len(const char **text, struct pattern *pattern, enum pat_usage usage, int *opaque, char **err)
@@ -399,88 +393,65 @@ int pat_parse_len(const char **text, struct pattern *pattern, enum pat_usage usa
  */
 int pat_parse_dotted_ver(const char **text, struct pattern *pattern, enum pat_usage usage, int *opaque, char **err)
 {
-       signed long long i;
-       unsigned int j, last, skip = 0;
        const char *ptr = *text;
 
+       pattern->type = SMP_T_UINT;
+       pattern->expect_type = SMP_T_UINT;
 
-       while (!isdigit((unsigned char)*ptr)) {
-               switch (get_std_op(ptr)) {
-               case STD_OP_EQ: *opaque = 0; break;
-               case STD_OP_GT: *opaque = 1; break;
-               case STD_OP_GE: *opaque = 2; break;
-               case STD_OP_LT: *opaque = 3; break;
-               case STD_OP_LE: *opaque = 4; break;
-               default:
-                       memprintf(err, "'%s' is neither a number nor a supported operator", ptr);
+       /* Search ':' or '-' separator. */
+       while (*ptr != '\0' && *ptr != ':' && *ptr != '-')
+               ptr++;
+
+       /* If separator not found. */
+       if (*ptr == '\0' && ptr > *text) {
+               if (strl2llrc_dotted(*text, ptr-*text, &pattern->val.range.min) != 0) {
+                       memprintf(err, "'%s' is not a dotted number", *text);
                        return 0;
                }
-
-               skip++;
-               ptr = text[skip];
+               pattern->val.range.max = pattern->val.range.min;
+               pattern->val.range.min_set = 1;
+               pattern->val.range.max_set = 1;
+               return 1;
        }
 
-       last = i = 0;
-       while (1) {
-                j = *ptr++;
-               if (j == '.') {
-                       /* minor part */
-                       if (i >= 65536)
-                               return 0;
-                       i <<= 16;
-                       continue;
-               }
-               if ((j == '-' || j == ':') && !last) {
-                       last++;
-                       if (i < 65536)
-                               i <<= 16;
-                       pattern->val.range.min = i;
-                       i = 0;
-                       continue;
+       /* If the separator is the first character. */
+       if (ptr == *text && *(ptr+1) != '\0') {
+               if (strl2llrc_dotted(ptr+1, strlen(ptr+1), &pattern->val.range.max) != 0) {
+                       memprintf(err, "'%s' is not a valid dotted number range", *text);
+                       return 0;
                }
-               j -= '0';
-                if (j > 9)
-                       // also catches the terminating zero
-                        break;
-                i = (i & 0xFFFF0000) + (i & 0xFFFF) * 10;
-                i += j;
-        }
-
-       /* if we only got a major version, let's shift it now */
-       if (i < 65536)
-               i <<= 16;
-
-       if (last && *opaque >= 1 && *opaque <= 4) {
-               /* having a range with a min or a max is absurd */
-               memprintf(err, "version range '%s' specified with a comparison operator", text[skip]);
-               return 0;
+               pattern->val.range.min_set = 0;
+               pattern->val.range.max_set = 1;
+               return 1;
        }
 
-       pattern->expect_type = SMP_T_UINT;
-
-       if (!last)
-               pattern->val.range.min = i;
-       pattern->val.range.max = i;
-
-       switch (*opaque) {
-       case 0: /* eq */
-               pattern->val.range.min_set = 1;
-               pattern->val.range.max_set = 1;
-               break;
-       case 1: /* gt */
-               pattern->val.range.min++; /* gt = ge + 1 */
-       case 2: /* ge */
+       /* If separator is the last character. */
+       if (ptr == &(*text)[strlen(*text)-1]) {
+               if (strl2llrc_dotted(*text, ptr-*text, &pattern->val.range.min) != 0) {
+                       memprintf(err, "'%s' is not a valid dotted number range", *text);
+                       return 0;
+               }
                pattern->val.range.min_set = 1;
                pattern->val.range.max_set = 0;
-               break;
-       case 3: /* lt */
-               pattern->val.range.max--; /* lt = le - 1 */
-       case 4: /* le */
-               pattern->val.range.min_set = 0;
-               pattern->val.range.max_set = 1;
-               break;
+               return 1;
+       }
+
+       /* Else, parse two numbers. */
+       if (strl2llrc_dotted(*text, ptr-*text, &pattern->val.range.min) != 0) {
+               memprintf(err, "'%s' is not a valid dotted number range", *text);
+               return 0;
+       }
+       if (strl2llrc_dotted(ptr+1, strlen(ptr+1), &pattern->val.range.max) != 0) {
+               memprintf(err, "'%s' is not a valid dotted number range", *text);
+               return 0;
        }
-       return skip + 1;
+       if (pattern->val.range.min > pattern->val.range.max) {
+               memprintf(err, "'%s' is not a valid dotted number range", *text);
+               return 0;
+       }
+       pattern->val.range.min_set = 1;
+       pattern->val.range.max_set = 1;
+       return 1;
 }
 
 /* Parse an IP address and an optional mask in the form addr[/mask].
index 46c940d66ca0781986893e2a674d0fcaa1974685..81df8b194915d3020da71df92e9010b83c63c090 100644 (file)
@@ -1257,6 +1257,50 @@ int strl2llrc(const char *s, int len, long long *ret)
        return 0;
 }
 
+/* This function is used with pat_parse_dotted_ver(). It converts a string
+ * composed by two number separated by a dot. Each part must contain in 16 bits
+ * because internally they will be represented as a 32-bit quantity stored in
+ * a 64-bit integer. It returns zero when the number has successfully been
+ * converted, non-zero otherwise. When an error is returned, the <ret> value
+ * is left untouched.
+ *
+ *    "1.3"         -> 0x0000000000010003
+ *    "65535.65535" -> 0x00000000ffffffff
+ */
+int strl2llrc_dotted(const char *text, int len, long long *ret)
+{
+       const char *end = &text[len];
+       const char *p;
+       long long major, minor;
+
+       /* Look for dot. */
+       for (p = text; p < end; p++)
+               if (*p == '.')
+                       break;
+
+       /* Convert major. */
+       if (strl2llrc(text, p - text, &major) != 0)
+               return 1;
+
+       /* Check major. */
+       if (major >= 65536)
+               return 1;
+
+       /* Convert minor. */
+       minor = 0;
+       if (p < end)
+               if (strl2llrc(p + 1, end - (p + 1), &minor) != 0)
+                       return 1;
+
+       /* Check minor. */
+       if (minor >= 65536)
+               return 1;
+
+       /* Compose value. */
+       *ret = (major << 16) | (minor & 0xffff);
+       return 0;
+}
+
 /* This function parses a time value optionally followed by a unit suffix among
  * "d", "h", "m", "s", "ms" or "us". It converts the value into the unit
  * expected by the caller. The computation does its best to avoid overflows.