From: "Daniel P. Berrange" <berrange@xxxxxxxxxx> To facilitate parsing of argv[] style strings, provide a virStrSplitQuoted API which will split a string on the listed separators, but also allow for quoting with ' or ". * src/libvirt_private.syms, src/util/util.c, src/util/util.h: Implement virStrSplitQuoted * tests/utiltest.c: Some tests for virStrSplitQuoted --- src/libvirt_private.syms | 1 + src/util/util.c | 117 ++++++++++++++++++++++++++++++++++++++++++++++ src/util/util.h | 2 + tests/utiltest.c | 89 ++++++++++++++++++++++++++++++++-- 4 files changed, 203 insertions(+), 6 deletions(-) diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 1f55f5d..b6fbafc 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -1148,6 +1148,7 @@ virSetUIDGID; virSkipSpaces; virSkipSpacesAndBackslash; virSkipSpacesBackwards; +virStrSplitQuoted; virStrToDouble; virStrToLong_i; virStrToLong_l; diff --git a/src/util/util.c b/src/util/util.c index 15e6cfa..acfb033 100644 --- a/src/util/util.c +++ b/src/util/util.c @@ -1951,6 +1951,123 @@ virStrcpy(char *dest, const char *src, size_t destbytes) return virStrncpy(dest, src, strlen(src), destbytes); } + +static char * +virStrDupUnescape(const char *src, size_t len) +{ + char *ret; + size_t i, j; + bool escape = false; + + if (VIR_ALLOC_N(ret, len + 1) < 0) + return NULL; + + for (i = 0, j = 0 ; i < len ; i++) { + if (escape) { + escape = false; + ret[j++] = src[i]; + } else if (src[i] == '\\') { + escape = true; + } else { + ret[j++] = src[i]; + } + } + ret[j++] = '\0'; + + return ret; +} + +/** + * virStrSplitQuoted: + * @src: string to split + * @sep: list of separator characters + * + * Split the string 'src' into chunks, separated by one or more of + * the characters in 'sep'. Allow ' or " to quote strings even if + * they contain 'sep' + * + * Returns NULL terminated array of strings + */ +char ** +virStrSplitQuoted(const char *src, const char *sep) +{ + size_t alloc = 0; + size_t count = 0; + char **ret = NULL; + const char *start = src; + const char *end; + bool escape; + char match; + char *value = NULL; + size_t i; + + while (start && *start) { + start += strspn(start, sep); + + if (!*start) + break; + + if (*start == '\'' || *start == '\"') { + match = *start; + start++; + + for (end = start, escape = false ; *end ; end++) { + if (escape) + escape = false; + else if (*end == '\\') + escape = true; + else if (*end == match) + break; + } + + if (VIR_RESIZE_N(ret, alloc, count, 1) < 0) + goto no_memory; + + if (!(ret[count] = virStrDupUnescape(start, end-start))) + goto no_memory; + count++; + + start = end; + if (*start) + start++; + } else { + for (end = start, escape = false ; *end ; end++) { + if (escape) + escape = false; + else if (*end == '\\') + escape = true; + else if (strspn(end, sep)) + break; + } + + if (VIR_RESIZE_N(ret, alloc, count, 1) < 0) + goto no_memory; + + if (!(ret[count] = virStrDupUnescape(start, end-start))) + goto no_memory; + count++; + + start = end; + if (*start) + start++; + } + } + + if (VIR_RESIZE_N(ret, alloc, count, 1) < 0) + goto no_memory; + ret[count] = NULL; + + return ret; + +no_memory: + VIR_FREE(value); + for (i = 0 ; i < count ; i++) + VIR_FREE(ret[i]); + VIR_FREE(ret); + return NULL; +} + + int virEnumFromString(const char *const*types, unsigned int ntypes, const char *type) diff --git a/src/util/util.h b/src/util/util.h index 85e8bd6..404003d 100644 --- a/src/util/util.h +++ b/src/util/util.h @@ -182,6 +182,8 @@ char *virStrcpy(char *dest, const char *src, size_t destbytes) ATTRIBUTE_RETURN_CHECK; # define virStrcpyStatic(dest, src) virStrcpy((dest), (src), sizeof(dest)) +char **virStrSplitQuoted(const char *src, const char *sep); + int virDiskNameToIndex(const char* str); char *virIndexToDiskName(int idx, const char *prefix); diff --git a/tests/utiltest.c b/tests/utiltest.c index 774a2f7..b8c6a8e 100644 --- a/tests/utiltest.c +++ b/tests/utiltest.c @@ -151,6 +151,55 @@ testParseVersionString(const void *data ATTRIBUTE_UNUSED) } +struct StringSplitData { + const char *src; + const char *sep; + const char **bits; +}; + +static int +testStringSplit(const void *opaque) +{ + int ret = -1; + const struct StringSplitData *data = opaque; + char **actual; + char **tmp1; + const char **tmp2 = data->bits; + size_t i; + + tmp1 = actual = virStrSplitQuoted(data->src, data->sep); + + if (!tmp1) + return -1; + + while (*tmp1 && *tmp2) { + if (STRNEQ(*tmp1, *tmp2)) { + fprintf(stderr, "Expected '%s' got '%s'\n", *tmp2, *tmp1); + goto cleanup; + } + + tmp1++; + tmp2++; + } + + if (*tmp1) { + fprintf(stderr, "Unexpected extra value '%s'\n", *tmp1); + goto cleanup; + } + + if (*tmp2) { + fprintf(stderr, "Unexpected missing value '%s'\n", *tmp2); + goto cleanup; + } + + + ret = 0; +cleanup: + for (i = 0 ; actual[i] ; i++) + VIR_FREE(actual[i]); + VIR_FREE(actual); + return ret; +} static int @@ -161,17 +210,45 @@ mymain(void) virSetErrorFunc(NULL, testQuietError); #define DO_TEST(_name) \ - do { \ - if (virtTestRun("Util "#_name, 1, test##_name, \ - NULL) < 0) { \ - result = -1; \ - } \ - } while (0) + do { \ + if (virtTestRun("Util "#_name, 1, test##_name, \ + NULL) < 0) { \ + result = -1; \ + } \ + } while (0) + +#define DO_TEST_STRING(str, sep, bits) \ + do { \ + struct StringSplitData data = { \ + str, sep, bits \ + }; \ + if (virtTestRun("Util split " str, 1, testStringSplit, \ + &data) < 0) { \ + result = -1; \ + } \ + } while (0) DO_TEST(IndexToDiskName); DO_TEST(DiskNameToIndex); DO_TEST(ParseVersionString); + const char *bits1[] = { "foo", "bar", NULL }; + DO_TEST_STRING("foo bar", " ", bits1); + DO_TEST_STRING("foo 'bar'", " ", bits1); + DO_TEST_STRING("foo \"bar\"", " ", bits1); + DO_TEST_STRING(" foo \"bar\"", " ", bits1); + DO_TEST_STRING(" foo \"bar\"", " ", bits1); + DO_TEST_STRING(" foo \"bar\"\n ", " \t\n\r", bits1); + + const char *bits2[] = { "foo", "bar wizz", "eek", NULL }; + DO_TEST_STRING("foo 'bar wizz' eek", " ", bits2); + DO_TEST_STRING("foo \"bar wizz\" eek", " ", bits2); + + const char *bits3[] = { "foo", "'bar' \"wizz\"", "eek", NULL }; + DO_TEST_STRING("foo '\\'bar\\' \\\"wizz\\\"' eek", " ", bits3); + DO_TEST_STRING("foo \"\\'bar\\' \\\"wizz\\\"\" eek", " ", bits3); + DO_TEST_STRING("foo \"\\'bar\\' \\\"wizz\\\"\"\r\n eek", " \r\n", bits3); + return result == 0 ? EXIT_SUCCESS : EXIT_FAILURE; } -- 1.7.7.6 -- libvir-list mailing list libvir-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/libvir-list