/* SPDX-License-Identifier: LGPL-2.1-or-later */ #include "sd-escape.h" #include /* The following is copied from these files in systemd with minor * modifications: * - src/basic/escape.c * - src/basic/utf8.c * - src/basic/hexdecoct.c */ static int unhexchar(char c) { if (c >= '0' && c <= '9') return c - '0'; if (c >= 'a' && c <= 'f') return c - 'a' + 10; if (c >= 'A' && c <= 'F') return c - 'A' + 10; return -EINVAL; } static gboolean unichar_is_valid(guint32 ch) { if (ch >= 0x110000) /* End of unicode space */ return FALSE; if ((ch & 0xFFFFF800) == 0xD800) /* Reserved area for UTF-16 */ return FALSE; if ((ch >= 0xFDD0) && (ch <= 0xFDEF)) /* Reserved */ return FALSE; if ((ch & 0xFFFE) == 0xFFFE) /* BOM (Byte Order Mark) */ return FALSE; return TRUE; } static int unoctchar(char c) { if (c >= '0' && c <= '7') return c - '0'; return -EINVAL; } int cunescape_one(const char *p, gsize length, guint32 *ret, gboolean *eight_bit, gboolean accept_nul) { int r = 1; g_assert(p); g_assert(ret); /* Unescapes C style. Returns the unescaped character in ret. * Sets *eight_bit to true if the escaped sequence either fits in * one byte in UTF-8 or is a non-unicode literal byte and should * instead be copied directly. */ if (length != G_MAXSIZE && length < 1) return -EINVAL; switch (p[0]) { case 'a': *ret = '\a'; break; case 'b': *ret = '\b'; break; case 'f': *ret = '\f'; break; case 'n': *ret = '\n'; break; case 'r': *ret = '\r'; break; case 't': *ret = '\t'; break; case 'v': *ret = '\v'; break; case '\\': *ret = '\\'; break; case '"': *ret = '"'; break; case '\'': *ret = '\''; break; case 's': /* This is an extension of the XDG syntax files */ *ret = ' '; break; case 'x': { /* hexadecimal encoding */ int a, b; if (length != G_MAXSIZE && length < 3) return -EINVAL; a = unhexchar(p[1]); if (a < 0) return -EINVAL; b = unhexchar(p[2]); if (b < 0) return -EINVAL; /* Don't allow NUL bytes */ if (a == 0 && b == 0 && !accept_nul) return -EINVAL; *ret = (a << 4U) | b; *eight_bit = TRUE; r = 3; break; } case 'u': { /* C++11 style 16bit unicode */ int a[4]; gsize i; guint32 c; if (length != G_MAXSIZE && length < 5) return -EINVAL; for (i = 0; i < 4; i++) { a[i] = unhexchar(p[1 + i]); if (a[i] < 0) return a[i]; } c = ((guint32) a[0] << 12U) | ((guint32) a[1] << 8U) | ((guint32) a[2] << 4U) | (guint32) a[3]; /* Don't allow 0 chars */ if (c == 0 && !accept_nul) return -EINVAL; *ret = c; r = 5; break; } case 'U': { /* C++11 style 32bit unicode */ int a[8]; gsize i; guint32 c; if (length != G_MAXSIZE && length < 9) return -EINVAL; for (i = 0; i < 8; i++) { a[i] = unhexchar(p[1 + i]); if (a[i] < 0) return a[i]; } c = ((guint32) a[0] << 28U) | ((guint32) a[1] << 24U) | ((guint32) a[2] << 20U) | ((guint32) a[3] << 16U) | ((guint32) a[4] << 12U) | ((guint32) a[5] << 8U) | ((guint32) a[6] << 4U) | (guint32) a[7]; /* Don't allow 0 chars */ if (c == 0 && !accept_nul) return -EINVAL; /* Don't allow invalid code points */ if (!unichar_is_valid(c)) return -EINVAL; *ret = c; r = 9; break; } case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': { /* octal encoding */ int a, b, c; guint32 m; if (length != G_MAXSIZE && length < 3) return -EINVAL; a = unoctchar(p[0]); if (a < 0) return -EINVAL; b = unoctchar(p[1]); if (b < 0) return -EINVAL; c = unoctchar(p[2]); if (c < 0) return -EINVAL; /* don't allow NUL bytes */ if (a == 0 && b == 0 && c == 0 && !accept_nul) return -EINVAL; /* Don't allow bytes above 255 */ m = ((guint32) a << 6U) | ((guint32) b << 3U) | (guint32) c; if (m > 255) return -EINVAL; *ret = m; *eight_bit = TRUE; r = 3; break; } default: return -EINVAL; } return r; } /** * utf8_encode_unichar() - Encode single UCS-4 character as UTF-8 * @out_utf8: output buffer of at least 4 bytes or NULL * @g: UCS-4 character to encode * * This encodes a single UCS-4 character as UTF-8 and writes it into @out_utf8. * The length of the character is returned. It is not zero-terminated! If the * output buffer is NULL, only the length is returned. * * Returns: The length in bytes that the UTF-8 representation does or would * occupy. */ static gsize utf8_encode_unichar(char *out_utf8, guint32 g) { if (g < (1 << 7)) { if (out_utf8) out_utf8[0] = g & 0x7f; return 1; } else if (g < (1 << 11)) { if (out_utf8) { out_utf8[0] = 0xc0 | ((g >> 6) & 0x1f); out_utf8[1] = 0x80 | (g & 0x3f); } return 2; } else if (g < (1 << 16)) { if (out_utf8) { out_utf8[0] = 0xe0 | ((g >> 12) & 0x0f); out_utf8[1] = 0x80 | ((g >> 6) & 0x3f); out_utf8[2] = 0x80 | (g & 0x3f); } return 3; } else if (g < (1 << 21)) { if (out_utf8) { out_utf8[0] = 0xf0 | ((g >> 18) & 0x07); out_utf8[1] = 0x80 | ((g >> 12) & 0x3f); out_utf8[2] = 0x80 | ((g >> 6) & 0x3f); out_utf8[3] = 0x80 | (g & 0x3f); } return 4; } return 0; } gssize cunescape_length_with_prefix(const char *s, gsize length, const char *prefix, UnescapeFlags flags, char **ret) { g_autofree char *ans = NULL; char *t; const char *f; gsize pl; int r; g_assert(s); g_assert(ret); /* Undoes C style string escaping, and optionally prefixes it. */ if (!prefix) pl = 0; else pl = strlen(prefix); ans = g_new(char, pl+length+1); if (!ans) return -ENOMEM; if (prefix) memcpy(ans, prefix, pl); for (f = s, t = ans + pl; f < s + length; f++) { gsize remaining; gboolean eight_bit = FALSE; guint32 u; remaining = s + length - f; g_assert(remaining > 0); if (*f != '\\') { /* A literal, copy verbatim */ *(t++) = *f; continue; } if (remaining == 1) { if (flags & UNESCAPE_RELAX) { /* A trailing backslash, copy verbatim */ *(t++) = *f; continue; } return -EINVAL; } r = cunescape_one(f + 1, remaining - 1, &u, &eight_bit, flags & UNESCAPE_ACCEPT_NUL); if (r < 0) { if (flags & UNESCAPE_RELAX) { /* Invalid escape code, let's take it literal then */ *(t++) = '\\'; continue; } return r; } f += r; if (eight_bit) /* One byte? Set directly as specified */ *(t++) = u; else /* Otherwise encode as multi-byte UTF-8 */ t += utf8_encode_unichar(t, u); } *t = 0; g_assert(t >= ans); /* Let static analyzers know that the answer is non-negative. */ *ret = g_steal_pointer (&ans); return t - *ret; }