From c8250381c8272a9828fdd353171727b154fbd296 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 13 Oct 2014 15:55:16 -0700 Subject: lib / string_helpers: introduce string_escape_mem() This is almost the opposite function to string_unescape(). Nevertheless it handles \0 and could be used for any byte buffer. The documentation is supplied together with the function prototype. The test cases covers most of the scenarios and would be expanded later on. [akpm@linux-foundation.org: avoid 1k stack consumption] Signed-off-by: Andy Shevchenko Cc: "John W . Linville" Cc: Johannes Berg Cc: Greg Kroah-Hartman Cc: Joe Perches Cc: Wu Fengguang Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- lib/string_helpers.c | 274 ++++++++++++++++++++++++++++++++++++++++++++++ lib/test-string_helpers.c | 240 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 510 insertions(+), 4 deletions(-) (limited to 'lib') diff --git a/lib/string_helpers.c b/lib/string_helpers.c index 74ec60469640..58b78ba57439 100644 --- a/lib/string_helpers.c +++ b/lib/string_helpers.c @@ -8,6 +8,8 @@ #include #include #include +#include +#include #include /** @@ -240,3 +242,275 @@ int string_unescape(char *src, char *dst, size_t size, unsigned int flags) return out - dst; } EXPORT_SYMBOL(string_unescape); + +static int escape_passthrough(unsigned char c, char **dst, size_t *osz) +{ + char *out = *dst; + + if (*osz < 1) + return -ENOMEM; + + *out++ = c; + + *dst = out; + *osz -= 1; + + return 1; +} + +static int escape_space(unsigned char c, char **dst, size_t *osz) +{ + char *out = *dst; + unsigned char to; + + if (*osz < 2) + return -ENOMEM; + + switch (c) { + case '\n': + to = 'n'; + break; + case '\r': + to = 'r'; + break; + case '\t': + to = 't'; + break; + case '\v': + to = 'v'; + break; + case '\f': + to = 'f'; + break; + default: + return 0; + } + + *out++ = '\\'; + *out++ = to; + + *dst = out; + *osz -= 2; + + return 1; +} + +static int escape_special(unsigned char c, char **dst, size_t *osz) +{ + char *out = *dst; + unsigned char to; + + if (*osz < 2) + return -ENOMEM; + + switch (c) { + case '\\': + to = '\\'; + break; + case '\a': + to = 'a'; + break; + case '\e': + to = 'e'; + break; + default: + return 0; + } + + *out++ = '\\'; + *out++ = to; + + *dst = out; + *osz -= 2; + + return 1; +} + +static int escape_null(unsigned char c, char **dst, size_t *osz) +{ + char *out = *dst; + + if (*osz < 2) + return -ENOMEM; + + if (c) + return 0; + + *out++ = '\\'; + *out++ = '0'; + + *dst = out; + *osz -= 2; + + return 1; +} + +static int escape_octal(unsigned char c, char **dst, size_t *osz) +{ + char *out = *dst; + + if (*osz < 4) + return -ENOMEM; + + *out++ = '\\'; + *out++ = ((c >> 6) & 0x07) + '0'; + *out++ = ((c >> 3) & 0x07) + '0'; + *out++ = ((c >> 0) & 0x07) + '0'; + + *dst = out; + *osz -= 4; + + return 1; +} + +static int escape_hex(unsigned char c, char **dst, size_t *osz) +{ + char *out = *dst; + + if (*osz < 4) + return -ENOMEM; + + *out++ = '\\'; + *out++ = 'x'; + *out++ = hex_asc_hi(c); + *out++ = hex_asc_lo(c); + + *dst = out; + *osz -= 4; + + return 1; +} + +/** + * string_escape_mem - quote characters in the given memory buffer + * @src: source buffer (unescaped) + * @isz: source buffer size + * @dst: destination buffer (escaped) + * @osz: destination buffer size + * @flags: combination of the flags (bitwise OR): + * %ESCAPE_SPACE: + * '\f' - form feed + * '\n' - new line + * '\r' - carriage return + * '\t' - horizontal tab + * '\v' - vertical tab + * %ESCAPE_SPECIAL: + * '\\' - backslash + * '\a' - alert (BEL) + * '\e' - escape + * %ESCAPE_NULL: + * '\0' - null + * %ESCAPE_OCTAL: + * '\NNN' - byte with octal value NNN (3 digits) + * %ESCAPE_ANY: + * all previous together + * %ESCAPE_NP: + * escape only non-printable characters (checked by isprint) + * %ESCAPE_ANY_NP: + * all previous together + * %ESCAPE_HEX: + * '\xHH' - byte with hexadecimal value HH (2 digits) + * @esc: NULL-terminated string of characters any of which, if found in + * the source, has to be escaped + * + * Description: + * The process of escaping byte buffer includes several parts. They are applied + * in the following sequence. + * 1. The character is matched to the printable class, if asked, and in + * case of match it passes through to the output. + * 2. The character is not matched to the one from @esc string and thus + * must go as is to the output. + * 3. The character is checked if it falls into the class given by @flags. + * %ESCAPE_OCTAL and %ESCAPE_HEX are going last since they cover any + * character. Note that they actually can't go together, otherwise + * %ESCAPE_HEX will be ignored. + * + * Caller must provide valid source and destination pointers. Be aware that + * destination buffer will not be NULL-terminated, thus caller have to append + * it if needs. + * + * Return: + * The amount of the characters processed to the destination buffer, or + * %-ENOMEM if the size of buffer is not enough to put an escaped character is + * returned. + * + * Even in the case of error @dst pointer will be updated to point to the byte + * after the last processed character. + */ +int string_escape_mem(const char *src, size_t isz, char **dst, size_t osz, + unsigned int flags, const char *esc) +{ + char *out = *dst, *p = out; + bool is_dict = esc && *esc; + int ret = 0; + + while (isz--) { + unsigned char c = *src++; + + /* + * Apply rules in the following sequence: + * - the character is printable, when @flags has + * %ESCAPE_NP bit set + * - the @esc string is supplied and does not contain a + * character under question + * - the character doesn't fall into a class of symbols + * defined by given @flags + * In these cases we just pass through a character to the + * output buffer. + */ + if ((flags & ESCAPE_NP && isprint(c)) || + (is_dict && !strchr(esc, c))) { + /* do nothing */ + } else { + if (flags & ESCAPE_SPACE) { + ret = escape_space(c, &p, &osz); + if (ret < 0) + break; + if (ret > 0) + continue; + } + + if (flags & ESCAPE_SPECIAL) { + ret = escape_special(c, &p, &osz); + if (ret < 0) + break; + if (ret > 0) + continue; + } + + if (flags & ESCAPE_NULL) { + ret = escape_null(c, &p, &osz); + if (ret < 0) + break; + if (ret > 0) + continue; + } + + /* ESCAPE_OCTAL and ESCAPE_HEX always go last */ + if (flags & ESCAPE_OCTAL) { + ret = escape_octal(c, &p, &osz); + if (ret < 0) + break; + continue; + } + if (flags & ESCAPE_HEX) { + ret = escape_hex(c, &p, &osz); + if (ret < 0) + break; + continue; + } + } + + ret = escape_passthrough(c, &p, &osz); + if (ret < 0) + break; + } + + *dst = p; + + if (ret < 0) + return ret; + + return p - out; +} +EXPORT_SYMBOL(string_escape_mem); diff --git a/lib/test-string_helpers.c b/lib/test-string_helpers.c index ac44c9245dcf..ab0d30e1e18f 100644 --- a/lib/test-string_helpers.c +++ b/lib/test-string_helpers.c @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -62,10 +63,14 @@ static const struct test_string strings[] __initconst = { static void __init test_string_unescape(const char *name, unsigned int flags, bool inplace) { - char in[256]; - char out_test[256]; - char out_real[256]; - int i, p = 0, q_test = 0, q_real = sizeof(out_real); + int q_real = 256; + char *in = kmalloc(q_real, GFP_KERNEL); + char *out_test = kmalloc(q_real, GFP_KERNEL); + char *out_real = kmalloc(q_real, GFP_KERNEL); + int i, p = 0, q_test = 0; + + if (!in || !out_test || !out_real) + goto out; for (i = 0; i < ARRAY_SIZE(strings); i++) { const char *s = strings[i].in; @@ -100,6 +105,223 @@ static void __init test_string_unescape(const char *name, unsigned int flags, test_string_check_buf(name, flags, in, p - 1, out_real, q_real, out_test, q_test); +out: + kfree(out_real); + kfree(out_test); + kfree(in); +} + +struct test_string_1 { + const char *out; + unsigned int flags; +}; + +#define TEST_STRING_2_MAX_S1 32 +struct test_string_2 { + const char *in; + struct test_string_1 s1[TEST_STRING_2_MAX_S1]; +}; + +#define TEST_STRING_2_DICT_0 NULL +static const struct test_string_2 escape0[] __initconst = {{ + .in = "\f\\ \n\r\t\v", + .s1 = {{ + .out = "\\f\\ \\n\\r\\t\\v", + .flags = ESCAPE_SPACE, + },{ + .out = "\\f\\134\\040\\n\\r\\t\\v", + .flags = ESCAPE_SPACE | ESCAPE_OCTAL, + },{ + .out = "\\f\\x5c\\x20\\n\\r\\t\\v", + .flags = ESCAPE_SPACE | ESCAPE_HEX, + },{ + /* terminator */ + }}, +},{ + .in = "\\h\\\"\a\e\\", + .s1 = {{ + .out = "\\\\h\\\\\"\\a\\e\\\\", + .flags = ESCAPE_SPECIAL, + },{ + .out = "\\\\\\150\\\\\\042\\a\\e\\\\", + .flags = ESCAPE_SPECIAL | ESCAPE_OCTAL, + },{ + .out = "\\\\\\x68\\\\\\x22\\a\\e\\\\", + .flags = ESCAPE_SPECIAL | ESCAPE_HEX, + },{ + /* terminator */ + }}, +},{ + .in = "\eb \\C\007\"\x90\r]", + .s1 = {{ + .out = "\eb \\C\007\"\x90\\r]", + .flags = ESCAPE_SPACE, + },{ + .out = "\\eb \\\\C\\a\"\x90\r]", + .flags = ESCAPE_SPECIAL, + },{ + .out = "\\eb \\\\C\\a\"\x90\\r]", + .flags = ESCAPE_SPACE | ESCAPE_SPECIAL, + },{ + .out = "\\033\\142\\040\\134\\103\\007\\042\\220\\015\\135", + .flags = ESCAPE_OCTAL, + },{ + .out = "\\033\\142\\040\\134\\103\\007\\042\\220\\r\\135", + .flags = ESCAPE_SPACE | ESCAPE_OCTAL, + },{ + .out = "\\e\\142\\040\\\\\\103\\a\\042\\220\\015\\135", + .flags = ESCAPE_SPECIAL | ESCAPE_OCTAL, + },{ + .out = "\\e\\142\\040\\\\\\103\\a\\042\\220\\r\\135", + .flags = ESCAPE_SPACE | ESCAPE_SPECIAL | ESCAPE_OCTAL, + },{ + .out = "\eb \\C\007\"\x90\r]", + .flags = ESCAPE_NP, + },{ + .out = "\eb \\C\007\"\x90\\r]", + .flags = ESCAPE_SPACE | ESCAPE_NP, + },{ + .out = "\\eb \\C\\a\"\x90\r]", + .flags = ESCAPE_SPECIAL | ESCAPE_NP, + },{ + .out = "\\eb \\C\\a\"\x90\\r]", + .flags = ESCAPE_SPACE | ESCAPE_SPECIAL | ESCAPE_NP, + },{ + .out = "\\033b \\C\\007\"\\220\\015]", + .flags = ESCAPE_OCTAL | ESCAPE_NP, + },{ + .out = "\\033b \\C\\007\"\\220\\r]", + .flags = ESCAPE_SPACE | ESCAPE_OCTAL | ESCAPE_NP, + },{ + .out = "\\eb \\C\\a\"\\220\\r]", + .flags = ESCAPE_SPECIAL | ESCAPE_SPACE | ESCAPE_OCTAL | + ESCAPE_NP, + },{ + .out = "\\x1bb \\C\\x07\"\\x90\\x0d]", + .flags = ESCAPE_NP | ESCAPE_HEX, + },{ + /* terminator */ + }}, +},{ + /* terminator */ +}}; + +#define TEST_STRING_2_DICT_1 "b\\ \t\r" +static const struct test_string_2 escape1[] __initconst = {{ + .in = "\f\\ \n\r\t\v", + .s1 = {{ + .out = "\f\\134\\040\n\\015\\011\v", + .flags = ESCAPE_OCTAL, + },{ + .out = "\f\\x5c\\x20\n\\x0d\\x09\v", + .flags = ESCAPE_HEX, + },{ + /* terminator */ + }}, +},{ + .in = "\\h\\\"\a\e\\", + .s1 = {{ + .out = "\\134h\\134\"\a\e\\134", + .flags = ESCAPE_OCTAL, + },{ + /* terminator */ + }}, +},{ + .in = "\eb \\C\007\"\x90\r]", + .s1 = {{ + .out = "\e\\142\\040\\134C\007\"\x90\\015]", + .flags = ESCAPE_OCTAL, + },{ + /* terminator */ + }}, +},{ + /* terminator */ +}}; + +static __init const char *test_string_find_match(const struct test_string_2 *s2, + unsigned int flags) +{ + const struct test_string_1 *s1 = s2->s1; + unsigned int i; + + if (!flags) + return s2->in; + + /* Test cases are NULL-aware */ + flags &= ~ESCAPE_NULL; + + /* ESCAPE_OCTAL has a higher priority */ + if (flags & ESCAPE_OCTAL) + flags &= ~ESCAPE_HEX; + + for (i = 0; i < TEST_STRING_2_MAX_S1 && s1->out; i++, s1++) + if (s1->flags == flags) + return s1->out; + return NULL; +} + +static __init void test_string_escape(const char *name, + const struct test_string_2 *s2, + unsigned int flags, const char *esc) +{ + int q_real = 512; + char *out_test = kmalloc(q_real, GFP_KERNEL); + char *out_real = kmalloc(q_real, GFP_KERNEL); + char *in = kmalloc(256, GFP_KERNEL); + char *buf = out_real; + int p = 0, q_test = 0; + + if (!out_test || !out_real || !in) + goto out; + + for (; s2->in; s2++) { + const char *out; + int len; + + /* NULL injection */ + if (flags & ESCAPE_NULL) { + in[p++] = '\0'; + out_test[q_test++] = '\\'; + out_test[q_test++] = '0'; + } + + /* Don't try strings that have no output */ + out = test_string_find_match(s2, flags); + if (!out) + continue; + + /* Copy string to in buffer */ + len = strlen(s2->in); + memcpy(&in[p], s2->in, len); + p += len; + + /* Copy expected result for given flags */ + len = strlen(out); + memcpy(&out_test[q_test], out, len); + q_test += len; + } + + q_real = string_escape_mem(in, p, &buf, q_real, flags, esc); + + test_string_check_buf(name, flags, in, p, out_real, q_real, out_test, + q_test); +out: + kfree(in); + kfree(out_real); + kfree(out_test); +} + +static __init void test_string_escape_nomem(void) +{ + char *in = "\eb \\C\007\"\x90\r]"; + char out[64], *buf = out; + int rc = -ENOMEM, ret; + + ret = string_escape_str_any_np(in, &buf, strlen(in), NULL); + if (ret == rc) + return; + + pr_err("Test 'escape nomem' failed: got %d instead of %d\n", ret, rc); } static int __init test_string_helpers_init(void) @@ -112,6 +334,16 @@ static int __init test_string_helpers_init(void) test_string_unescape("unescape inplace", get_random_int() % (UNESCAPE_ANY + 1), true); + /* Without dictionary */ + for (i = 0; i < (ESCAPE_ANY_NP | ESCAPE_HEX) + 1; i++) + test_string_escape("escape 0", escape0, i, TEST_STRING_2_DICT_0); + + /* With dictionary */ + for (i = 0; i < (ESCAPE_ANY_NP | ESCAPE_HEX) + 1; i++) + test_string_escape("escape 1", escape1, i, TEST_STRING_2_DICT_1); + + test_string_escape_nomem(); + return -EINVAL; } module_init(test_string_helpers_init); -- cgit v1.2.3