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