File bnc925225-aug_escape.patch of Package augeas.6793
diff --git a/src/augeas.c b/src/augeas.c
index b31560d..ba50071 100644
--- a/src/augeas.c
+++ b/src/augeas.c
@@ -1984,6 +1984,20 @@ int aug_transform(struct augeas *aug, const char *lens,
return result;
}
+int aug_escape_name(augeas *aug, const char *in, char **out) {
+ int result;
+
+ api_entry(aug);
+ ARG_CHECK(in == NULL, aug, "aug_escape_name: IN must not be NULL");
+ ARG_CHECK(out == NULL, aug, "aug_escape_name: OUT must not be NULL");
+
+ result = pathx_escape_name(in, out);
+ ERR_NOMEM(result < 0, aug);
+ error:
+ api_exit(aug);
+ return result;
+}
+
int aug_print(const struct augeas *aug, FILE *out, const char *pathin) {
struct pathx *p;
int result;
diff --git a/src/augeas.h b/src/augeas.h
index b4d6003..3225651 100644
--- a/src/augeas.h
+++ b/src/augeas.h
@@ -370,6 +370,23 @@ int aug_text_retrieve(struct augeas *aug, const char *lens,
const char *node_in, const char *path,
const char *node_out);
+/* Function: aug_escape_name
+ *
+ * Escape special characters in a string such that it can be used as part
+ * of a path expressions and only matches a node named exactly
+ * IN. Characters that have special meanings in path expressions, such as
+ * '[' and ']' are prefixed with a '\\'. Note that this function assumes
+ * that it is passed a name, not a path, and will therefore escape '/',
+ * too.
+ *
+ * On return, *OUT is NULL if IN does not need any escaping at all, and
+ * points to an escaped copy of IN otherwise.
+ *
+ * Returns:
+ * 0 on success, or a negative value on failure
+ */
+int aug_escape_name(augeas *aug, const char *in, char **out);
+
/* Function: aug_print
*
* Print each node matching PATH and its descendants to OUT.
diff --git a/src/augeas_sym.version b/src/augeas_sym.version
index 3740f67..c128e8d 100644
--- a/src/augeas_sym.version
+++ b/src/augeas_sym.version
@@ -65,3 +65,8 @@ AUGEAS_0.18.0 {
global:
aug_cp;
} AUGEAS_0.16.0;
+
+AUGEAS_0.19.0 {
+ global:
+ aug_escape_name;
+} AUGEAS_0.18.0;
\ No newline at end of file
diff --git a/src/internal.c b/src/internal.c
index b9e3eab..e149fe6 100644
--- a/src/internal.c
+++ b/src/internal.c
@@ -336,6 +336,8 @@ char *path_expand(struct tree *tree, const char *ppath) {
char *path;
const char *label;
+ char *escaped = NULL;
+
int cnt = 0, ind = 0, r;
list_for_each(t, siblings) {
@@ -356,11 +358,21 @@ char *path_expand(struct tree *tree, const char *ppath) {
else
label = tree->label;
+ r = pathx_escape_name(label, &escaped);
+ if (r < 0)
+ return NULL;
+
+ if (escaped != NULL)
+ label = escaped;
+
if (cnt > 1) {
r = asprintf(&path, "%s/%s[%d]", ppath, label, ind);
} else {
r = asprintf(&path, "%s/%s", ppath, label);
}
+
+ free(escaped);
+
if (r == -1)
return NULL;
return path;
diff --git a/src/internal.h b/src/internal.h
index 0b4d5ac..85d8ee3 100644
--- a/src/internal.h
+++ b/src/internal.h
@@ -575,6 +575,16 @@ void pathx_symtab_remove_descendants(struct pathx_symtab *symtab,
const struct tree *tree);
void free_symtab(struct pathx_symtab *symtab);
+/* Escape a name so that it is safe to pass to parse_name and have it
+ * interpreted as the literal name of a path component.
+ *
+ * On return, *OUT will be NULL if IN does not need escaping, otherwise it
+ * will contain an escaped copy of IN which the caller must free.
+ *
+ * Returns -1 if it failed to allocate memory for *OUT, 0 on success
+ */
+int pathx_escape_name(const char *in, char **out);
+
/* Debug helpers, all defined in internal.c. When ENABLE_DEBUG is not
* set, they compile to nothing.
*/
diff --git a/src/pathx.c b/src/pathx.c
index 467e5d7..0c4974d 100644
--- a/src/pathx.c
+++ b/src/pathx.c
@@ -123,6 +123,15 @@ static const char *const axis_names[] = {
static const char *const axis_sep = "::";
+/* The characters that can follow a name in a location expression (aka path)
+ * The parser will assume that name (path component) is finished when it
+ * encounters any of these characters, unless they are escaped by preceding
+ * them with a '\\'.
+ *
+ * See parse_name for the gory details
+ */
+static const char const name_follow[] = "][|/=()!,";
+
/* Doubly linked list of location steps. Besides the information from the
* path expression, also contains information to iterate over a node set,
* in particular, the context node CTX for the step, and the current node
@@ -1607,17 +1616,43 @@ static void push_new_binary_op(enum binary_op op, struct state *state) {
push_expr(expr, state);
}
+int pathx_escape_name(const char *in, char **out) {
+ const char *p;
+ int num_to_escape = 0;
+ char *s;
+
+ *out = NULL;
+
+ for (p = in; *p; p++) {
+ if (strchr(name_follow, *p) || isspace(*p))
+ num_to_escape += 1;
+ }
+
+ if (num_to_escape == 0)
+ return 0;
+
+ if (ALLOC_N(*out, strlen(in) + num_to_escape + 1) < 0)
+ return -1;
+
+ for (p = in, s = *out; *p; p++) {
+ if (strchr(name_follow, *p) || isspace(*p))
+ *s++ = '\\';
+ *s++ = *p;
+ }
+ *s = '\0';
+ return 0;
+}
+
/*
* NameNoWS ::= [^][|/\= \t\n] | \\.
* NameWS ::= [^][|/\=] | \\.
* Name ::= NameNoWS NameWS* NameNoWS | NameNoWS
*/
static char *parse_name(struct state *state) {
- static const char const follow[] = "][|/=()!,";
const char *s = state->pos;
char *result;
- while (*state->pos != '\0' && strchr(follow, *state->pos) == NULL) {
+ while (*state->pos != '\0' && strchr(name_follow, *state->pos) == NULL) {
/* This is a hack: since we allow spaces in names, we need to avoid
* gobbling up stuff that is in follow(Name), e.g. 'or' so that
* things like [name1 or name2] still work.
diff --git a/src/transform.c b/src/transform.c
index 1026912..7a355d2 100644
--- a/src/transform.c
+++ b/src/transform.c
@@ -1330,21 +1330,34 @@ int text_retrieve(struct augeas *aug, const char *lens_name,
}
int remove_file(struct augeas *aug, struct tree *tree) {
- char *path = NULL;
- const char *filename = NULL;
const char *err_status = NULL;
char *dyn_err_status = NULL;
char *augsave = NULL, *augorig = NULL, *augorig_canon = NULL;
+ struct tree *path = NULL;
+ const char *file_path = NULL;
+ char *meta_path = NULL;
int r;
- path = path_of_tree(tree);
+ path = tree_child(tree, s_path);
if (path == NULL) {
+ err_status = "no child called 'path' for file entry";
+ goto error;
+ }
+ file_path = path->value + strlen(AUGEAS_FILES_TREE);
+ path = NULL;
+
+ if (file_path == NULL) {
+ err_status = "no path for file";
+ goto error;
+ }
+
+ meta_path = path_of_tree(tree);
+ if (meta_path == NULL) {
err_status = "path_of_tree";
goto error;
}
- filename = path + strlen(AUGEAS_META_FILES);
- if ((augorig = strappend(aug->root, filename + 1)) == NULL) {
+ if ((augorig = strappend(aug->root, file_path)) == NULL) {
err_status = "root_file";
goto error;
}
@@ -1359,7 +1372,7 @@ int remove_file(struct augeas *aug, struct tree *tree) {
}
}
- r = file_saved_event(aug, path + strlen(AUGEAS_META_TREE));
+ r = file_saved_event(aug, meta_path + strlen(AUGEAS_META_TREE));
if (r < 0) {
err_status = "saved_event";
goto error;
@@ -1389,9 +1402,10 @@ int remove_file(struct augeas *aug, struct tree *tree) {
goto error;
}
}
+ path = NULL;
tree_unlink(aug, tree);
done:
- free(path);
+ free(meta_path);
free(augorig);
free(augorig_canon);
free(augsave);
@@ -1400,9 +1414,9 @@ int remove_file(struct augeas *aug, struct tree *tree) {
{
const char *emsg =
dyn_err_status == NULL ? err_status : dyn_err_status;
- store_error(aug, filename, path, emsg, errno, NULL, NULL);
+ store_error(aug, file_path, meta_path, emsg, errno, NULL, NULL);
}
- free(path);
+ free(meta_path);
free(augorig);
free(augorig_canon);
free(augsave);
diff --git a/tests/run.tests b/tests/run.tests
index 39f8153..c74b644 100644
--- a/tests/run.tests
+++ b/tests/run.tests
@@ -440,13 +440,13 @@ test set-escaped-path-bracket 2
set /white\ space/\[section value
print /white\ space/\[section
prints
- /white space/[section = "value"
+ /white\ space/\[section = "value"
test set-squote-escaped-bracket 2
set '/augeas/\[section' value
print '/augeas/\[section'
prints
- /augeas/[section = "value"
+ /augeas/\[section = "value"
test set-squote-escaped-path 2
set '/white\ space' value
diff --git a/tests/test-api.c b/tests/test-api.c
index 9367c27..cc2cc87 100644
--- a/tests/test-api.c
+++ b/tests/test-api.c
@@ -625,6 +625,23 @@ static void testTextRetrieve(CuTest *tc) {
CuAssertStrEquals(tc, hosts, hosts_out);
}
+static void testAugEscape(CuTest *tc) {
+ static const char *const in = "a/[]b|=c()!, \td";
+ static const char *const exp = "a\\/\\[\\]b\\|\\=c\\(\\)\\!\\,\\ \\\td";
+ char *out;
+ struct augeas *aug;
+ int r;
+
+ aug = aug_init(root, loadpath, AUG_NO_STDINC|AUG_NO_LOAD);
+ CuAssertPtrNotNull(tc, aug);
+
+ r = aug_escape_name(aug, in, &out);
+ CuAssertRetSuccess(tc, r);
+
+ CuAssertStrEquals(tc, out, exp);
+ free(out);
+}
+
int main(void) {
char *output = NULL;
CuSuite* suite = CuSuiteNew();
@@ -643,6 +660,7 @@ int main(void) {
SUITE_ADD_TEST(suite, testToXml);
SUITE_ADD_TEST(suite, testTextStore);
SUITE_ADD_TEST(suite, testTextRetrieve);
+ SUITE_ADD_TEST(suite, testAugEscape);
abs_top_srcdir = getenv("abs_top_srcdir");
if (abs_top_srcdir == NULL)
diff --git a/tests/test-save.c b/tests/test-save.c
index f28f626..2fe90a9 100644
--- a/tests/test-save.c
+++ b/tests/test-save.c
@@ -266,6 +266,58 @@ static void testUmask022(CuTest *tc) {
testUmask(tc, 0022, 0644);
}
+/* Test that handling of 'strange' characters in path names works as
+ * expected. In particular, that paths with characters that have special
+ * meaning in path expressions are escaped properly.
+ *
+ * This test isn't all that specific to save, but since these tests set up
+ * a copy of tests/root/ that is modifiable, it was convenient to put this
+ * test here.
+ */
+static void testPathEscaping(CuTest *tc) {
+ /* Path expression with characters escaped */
+ static const char *const weird =
+ "/files/etc/sysconfig/network-scripts/ifcfg-weird\\ \\[\\!\\]\\ \\(used\\ to\\ fail\\)";
+ /* Path without any escaping */
+ static const char *const weird_no_escape =
+ "/files/etc/sysconfig/network-scripts/ifcfg-weird [!] (used to fail)";
+
+ char *fname = NULL, *s = NULL;
+ const char *v;
+ int r;
+
+ /* Construct the file name in the file system and check the file is there */
+ r = asprintf(&fname, "%s%s", root, weird_no_escape + strlen("/files"));
+ CuAssertPositive(tc, r);
+
+ r = access(fname, R_OK);
+ CuAssertIntEquals(tc, 0, r);
+
+ /* Make sure weird is in the tree */
+ r = aug_match(aug, weird, NULL);
+ CuAssertIntEquals(tc, 1, r);
+
+ /* Make sure we can get to the metadata about weird */
+ r = asprintf(&s, "/augeas%s/path", weird);
+ CuAssertPositive(tc, r);
+
+ r = aug_get(aug, s, &v);
+ CuAssertIntEquals(tc, 1, r);
+ CuAssertStrEquals(tc, weird_no_escape, v);
+
+ /* Delete it from the tree and save it; make sure it gets removed
+ from the file system */
+ r = aug_rm(aug, weird);
+ CuAssertPositive(tc, r);
+
+ r = aug_save(aug);
+ CuAssertRetSuccess(tc, r);
+
+ r = access(fname, R_OK);
+ CuAssertIntEquals(tc, -1, r);
+ CuAssertIntEquals(tc, ENOENT, errno);
+}
+
int main(void) {
char *output = NULL;
CuSuite* suite = CuSuiteNew();
@@ -293,6 +345,7 @@ int main(void) {
SUITE_ADD_TEST(suite, testUmask077);
SUITE_ADD_TEST(suite, testUmask027);
SUITE_ADD_TEST(suite, testUmask022);
+ SUITE_ADD_TEST(suite, testPathEscaping);
CuSuiteRun(suite);
CuSuiteSummary(suite, &output);
diff --git a/tests/xpath.tests b/tests/xpath.tests
index 41bb555..e89cf98 100644
--- a/tests/xpath.tests
+++ b/tests/xpath.tests
@@ -278,7 +278,7 @@ test union (/files/etc/yum.conf | /files/etc/yum.repos.d/*)/*/gpgcheck
# Paths with whitespace in them
test php1 $php/mail function
- /files/etc/php.ini/mail function
+ /files/etc/php.ini/mail\ function
test php2 $php[mail function]
/files/etc/php.ini
@@ -287,10 +287,10 @@ test php3 $php[count(mail function) = 1]
/files/etc/php.ini
test php4 $php/mail function/SMTP
- /files/etc/php.ini/mail function/SMTP = localhost
+ /files/etc/php.ini/mail\ function/SMTP = localhost
test php5 $php/mail\ function
- /files/etc/php.ini/mail function
+ /files/etc/php.ini/mail\ function
test expr-or /files/etc/group/root/*[self::gid or self::user]
/files/etc/group/root/gid = 0
@@ -315,3 +315,19 @@ test ctx_file etc/network/interfaces
test ctx_file_pred etc/network/interfaces/iface[. = "lo"]
/files/etc/network/interfaces/iface[1] = lo
+
+# Test matching with characters that need escaping in the filename
+test escape1 /files/etc/sysconfig/network-scripts/*
+ /files/etc/sysconfig/network-scripts/ifcfg-eth0
+ /files/etc/sysconfig/network-scripts/ifcfg-wlan0
+ /files/etc/sysconfig/network-scripts/ifcfg-weird\ \[\!\]\ \(used\ to\ fail\)
+ /files/etc/sysconfig/network-scripts/ifcfg-Auto_FRITZ\!Box_Fon_WLAN_7112
+ /files/etc/sysconfig/network-scripts/ifcfg-lo
+ /files/etc/sysconfig/network-scripts/ifcfg-Auto-ALICE-WLAN38_\(automatisch\)
+ /files/etc/sysconfig/network-scripts/ifcfg-br0
+
+test escape2 /files/etc/sysconfig/network-scripts/ifcfg-weird\ \[\!\]\ \(used\ to\ fail\)/DEVICE
+ /files/etc/sysconfig/network-scripts/ifcfg-weird\ \[\!\]\ \(used\ to\ fail\)/DEVICE = weird
+
+test escape3 /files/etc/sysconfig/network-scripts/*[DEVICE = 'weird']
+ /files/etc/sysconfig/network-scripts/ifcfg-weird\ \[\!\]\ \(used\ to\ fail\)