Skip to content

Commit

Permalink
lib / string_helpers: introduce string_escape_mem()
Browse files Browse the repository at this point in the history
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.

[[email protected]: avoid 1k stack consumption]
Signed-off-by: Andy Shevchenko <[email protected]>
Cc: "John W . Linville" <[email protected]>
Cc: Johannes Berg <[email protected]>
Cc: Greg Kroah-Hartman <[email protected]>
Cc: Joe Perches <[email protected]>
Cc: Wu Fengguang <[email protected]>
Signed-off-by: Andrew Morton <[email protected]>
Signed-off-by: Linus Torvalds <[email protected]>
  • Loading branch information
andy-shev authored and torvalds committed Oct 14, 2014
1 parent 45ff337 commit c825038
Show file tree
Hide file tree
Showing 3 changed files with 541 additions and 4 deletions.
31 changes: 31 additions & 0 deletions include/linux/string_helpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,35 @@ static inline int string_unescape_any_inplace(char *buf)
return string_unescape_any(buf, buf, 0);
}

#define ESCAPE_SPACE 0x01
#define ESCAPE_SPECIAL 0x02
#define ESCAPE_NULL 0x04
#define ESCAPE_OCTAL 0x08
#define ESCAPE_ANY \
(ESCAPE_SPACE | ESCAPE_OCTAL | ESCAPE_SPECIAL | ESCAPE_NULL)
#define ESCAPE_NP 0x10
#define ESCAPE_ANY_NP (ESCAPE_ANY | ESCAPE_NP)
#define ESCAPE_HEX 0x20

int string_escape_mem(const char *src, size_t isz, char **dst, size_t osz,
unsigned int flags, const char *esc);

static inline int string_escape_mem_any_np(const char *src, size_t isz,
char **dst, size_t osz, const char *esc)
{
return string_escape_mem(src, isz, dst, osz, ESCAPE_ANY_NP, esc);
}

static inline int string_escape_str(const char *src, char **dst, size_t sz,
unsigned int flags, const char *esc)
{
return string_escape_mem(src, strlen(src), dst, sz, flags, esc);
}

static inline int string_escape_str_any_np(const char *src, char **dst,
size_t sz, const char *esc)
{
return string_escape_str(src, dst, sz, ESCAPE_ANY_NP, esc);
}

#endif
274 changes: 274 additions & 0 deletions lib/string_helpers.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
#include <linux/math64.h>
#include <linux/export.h>
#include <linux/ctype.h>
#include <linux/errno.h>
#include <linux/string.h>
#include <linux/string_helpers.h>

/**
Expand Down Expand Up @@ -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);
Loading

0 comments on commit c825038

Please sign in to comment.