Blame SOURCES/0011-src-pathx.c-parse_name-correctly-handle-trailing-whi.patch

3b98a7
From d157f330acfe94a1f61bf766b485beb0e0dd7177 Mon Sep 17 00:00:00 2001
3b98a7
From: David Lutterkort <lutter@watzmann.net>
3b98a7
Date: Fri, 4 Aug 2017 17:13:52 -0700
3b98a7
Subject: [PATCH] * src/pathx.c (parse_name): correctly handle trailing
3b98a7
 whitespace in names
3b98a7
3b98a7
When a name ended in whitespace, we incorrectly assumed it was always ok to
3b98a7
trim that whitespace. That is not true if that whitespace is escaped,
3b98a7
i.e. if the path expression is something like '/x\ '. In that case, the
3b98a7
name really needs to be literally 'x ', i.e., we can not trim that
3b98a7
whitespace.
3b98a7
3b98a7
The incorrect behavior led to turning '/x\ ' first into 'x\' and then,
3b98a7
because we assume that '\' is always followed by a character inside the
3b98a7
string, when we removed the escaping '\', we would read beyond the end of
3b98a7
the intermediate string result; if we were lucky, that would lead to a
3b98a7
crash, otherwise we'd continue with junk.
3b98a7
3b98a7
We now make sure that escaped whitespace at the end of a string does not
3b98a7
get stripped, avoiding all these headaches.
3b98a7
3b98a7
Fixes RHBZ https://bugzilla.redhat.com/show_bug.cgi?id=1475621
3b98a7
---
3b98a7
 src/pathx.c        | 27 ++++++++++++++++-----
3b98a7
 tests/test-xpath.c | 59 ++++++++++++++++++++++++++++++++++++++++++++++
3b98a7
 2 files changed, 80 insertions(+), 6 deletions(-)
3b98a7
3b98a7
diff --git a/src/pathx.c b/src/pathx.c
3b98a7
index 8d8dbbbe..a726a032 100644
3b98a7
--- a/src/pathx.c
3b98a7
+++ b/src/pathx.c
3b98a7
@@ -1643,6 +1643,16 @@ int pathx_escape_name(const char *in, char **out) {
3b98a7
     return 0;
3b98a7
 }
3b98a7
 
3b98a7
+/* Return true if POS is preceded by an odd number of backslashes, i.e., if
3b98a7
+ * POS is escaped. Stop the search when we get to START */
3b98a7
+static bool backslash_escaped(const char *pos, const char *start) {
3b98a7
+    bool result=false;
3b98a7
+    while (pos-- > start && *pos == '\\') {
3b98a7
+        result = !result;
3b98a7
+    }
3b98a7
+    return result;
3b98a7
+}
3b98a7
+
3b98a7
 /*
3b98a7
  * NameNoWS ::= [^][|/\= \t\n] | \\.
3b98a7
  * NameWS   ::= [^][|/\=] | \\.
3b98a7
@@ -1652,11 +1662,14 @@ static char *parse_name(struct state *state) {
3b98a7
     const char *s = state->pos;
3b98a7
     char *result;
3b98a7
 
3b98a7
+    /* Advance state->pos until it points to the first character that is
3b98a7
+     * not part of a name. */
3b98a7
     while (*state->pos != '\0' && strchr(name_follow, *state->pos) == NULL) {
3b98a7
-        /* This is a hack: since we allow spaces in names, we need to avoid
3b98a7
-         * gobbling up stuff that is in follow(Name), e.g. 'or' so that
3b98a7
-         * things like [name1 or name2] still work.
3b98a7
-         */
3b98a7
+        /* Since we allow spaces in names, we need to avoid gobbling up
3b98a7
+         * stuff that is in follow(Name), e.g. 'or' so that things like
3b98a7
+         * [name1 or name2] still work. In other words, we'll parse 'x frob
3b98a7
+         * y' as one name, but for 'x or y', we consider 'x' a name in its
3b98a7
+         * own right. */
3b98a7
         if (STREQLEN(state->pos, " or ", strlen(" or ")) ||
3b98a7
             STREQLEN(state->pos, " and ", strlen(" and ")))
3b98a7
             break;
3b98a7
@@ -1671,10 +1684,12 @@ static char *parse_name(struct state *state) {
3b98a7
         state->pos += 1;
3b98a7
     }
3b98a7
 
3b98a7
-    /* Strip trailing white space */
3b98a7
+    /* Strip trailing white space. Make sure we respect escaped whitespace
3b98a7
+     * and don't strip it as in "x\\ " */
3b98a7
     if (state->pos > s) {
3b98a7
         state->pos -= 1;
3b98a7
-        while (isspace(*state->pos) && state->pos >= s)
3b98a7
+        while (isspace(*state->pos) && state->pos > s
3b98a7
+               && !backslash_escaped(state->pos, s))
3b98a7
             state->pos -= 1;
3b98a7
         state->pos += 1;
3b98a7
     }
3b98a7
diff --git a/tests/test-xpath.c b/tests/test-xpath.c
3b98a7
index 335e7bf8..dbba29e0 100644
3b98a7
--- a/tests/test-xpath.c
3b98a7
+++ b/tests/test-xpath.c
3b98a7
@@ -331,6 +331,62 @@ static int test_wrong_regexp_flag(struct augeas *aug) {
3b98a7
     return -1;
3b98a7
 }
3b98a7
 
3b98a7
+static int test_trailing_ws_in_name(struct augeas *aug) {
3b98a7
+    int r;
3b98a7
+
3b98a7
+    printf("%-30s ... ", "trailing_ws_in_name");
3b98a7
+
3b98a7
+    /* We used to incorrectly lop escaped whitespace off the end of a
3b98a7
+     * name. Make sure that we really create a tree node with label 'x '
3b98a7
+     * with the below set, and look for it in a number of ways to ensure we
3b98a7
+     * are not lopping off trailing whitespace. */
3b98a7
+    r = aug_set(aug, "/ws\\ ", "1");
3b98a7
+    if (r < 0) {
3b98a7
+        fprintf(stderr, "failed to set '/ws ': %d\n", r);
3b98a7
+        goto fail;
3b98a7
+    }
3b98a7
+    /* We did not create a node with label 'ws' */
3b98a7
+    r = aug_get(aug, "/ws", NULL);
3b98a7
+    if (r != 0) {
3b98a7
+        fprintf(stderr, "created '/ws' instead: %d\n", r);
3b98a7
+        goto fail;
3b98a7
+    }
3b98a7
+
3b98a7
+    /* We did not create a node with label 'ws\t' (this also checks that we
3b98a7
+     * don't create something like 'ws\\' by dropping the last whitespace
3b98a7
+     * character. */
3b98a7
+    r = aug_get(aug, "/ws\\\t", NULL);
3b98a7
+    if (r != 0) {
3b98a7
+        fprintf(stderr, "found '/ws\\t': %d\n", r);
3b98a7
+        goto fail;
3b98a7
+    }
3b98a7
+
3b98a7
+    /* But we did create 'ws ' */
3b98a7
+    r = aug_get(aug, "/ws\\ ", NULL);
3b98a7
+    if (r != 1) {
3b98a7
+        fprintf(stderr, "could not find '/ws ': %d\n", r);
3b98a7
+        goto fail;
3b98a7
+    }
3b98a7
+
3b98a7
+    /* If the whitespace is preceded by an even number of '\\' chars,
3b98a7
+     * whitespace must be stripped */
3b98a7
+    r = aug_set(aug, "/nows\\\\ ", "1");
3b98a7
+    if (r < 0) {
3b98a7
+        fprintf(stderr, "set of '/nows' failed: %d\n", r);
3b98a7
+        goto fail;
3b98a7
+    }
3b98a7
+    r = aug_get(aug, "/nows\\\\", NULL);
3b98a7
+    if (r != 1) {
3b98a7
+        fprintf(stderr, "could not get '/nows\\'\n");
3b98a7
+        goto fail;
3b98a7
+    }
3b98a7
+    printf("PASS\n");
3b98a7
+    return 0;
3b98a7
+ fail:
3b98a7
+    printf("FAIL\n");
3b98a7
+    return -1;
3b98a7
+}
3b98a7
+
3b98a7
 static int run_tests(struct test *tests, int argc, char **argv) {
3b98a7
     char *lensdir;
3b98a7
     struct augeas *aug = NULL;
3b98a7
@@ -374,6 +430,9 @@ static int run_tests(struct test *tests, int argc, char **argv) {
3b98a7
 
3b98a7
         if (test_wrong_regexp_flag(aug) < 0)
3b98a7
             result = EXIT_FAILURE;
3b98a7
+
3b98a7
+        if (test_trailing_ws_in_name(aug) < 0)
3b98a7
+            result = EXIT_FAILURE;
3b98a7
     }
3b98a7
     aug_close(aug);
3b98a7
 
3b98a7
-- 
c941cc
2.24.1
3b98a7