lib/json/json: Add function to unescape JSON-encoded strings

JSON strings require certain characters to be encoded, either by using
a single reverse solidus character "\" for a set of popular characters,
or by using a Unicode representation of "\uXXXXX". The jsmn library
doesn't handle unescaping for us, so we must implement this functionality
for ourselves.

Add a new function grub_json_unescape() that takes a potentially
escaped JSON string as input and returns a new unescaped string.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Reviewed-by: Daniel Kiper <dkiper@net-space.pl>
This commit is contained in:
Patrick Steinhardt 2022-08-15 17:52:59 +02:00 committed by Daniel Kiper
parent 5730424cb9
commit b4055ebb8b
2 changed files with 130 additions and 0 deletions

View File

@ -262,3 +262,121 @@ grub_json_getint64 (grub_int64_t *out, const grub_json_t *parent, const char *ke
return GRUB_ERR_NONE;
}
grub_err_t
grub_json_unescape (char **out, grub_size_t *outlen, const char *in, grub_size_t inlen)
{
grub_err_t ret = GRUB_ERR_NONE;
grub_size_t inpos, resultpos;
char *result;
if (out == NULL || outlen == NULL)
return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("output parameters are not set"));
result = grub_calloc (1, inlen + 1);
if (result == NULL)
return GRUB_ERR_OUT_OF_MEMORY;
for (inpos = resultpos = 0; inpos < inlen; inpos++)
{
if (in[inpos] == '\\')
{
inpos++;
if (inpos >= inlen)
{
ret = grub_error (GRUB_ERR_BAD_ARGUMENT, N_("expected escaped character"));
goto err;
}
switch (in[inpos])
{
case '"':
result[resultpos++] = '"';
break;
case '/':
result[resultpos++] = '/';
break;
case '\\':
result[resultpos++] = '\\';
break;
case 'b':
result[resultpos++] = '\b';
break;
case 'f':
result[resultpos++] = '\f';
break;
case 'r':
result[resultpos++] = '\r';
break;
case 'n':
result[resultpos++] = '\n';
break;
case 't':
result[resultpos++] = '\t';
break;
case 'u':
{
char values[4] = {0};
unsigned i;
inpos++;
if (inpos + ARRAY_SIZE(values) > inlen)
{
ret = grub_error (GRUB_ERR_BAD_ARGUMENT, N_("unicode sequence too short"));
goto err;
}
for (i = 0; i < ARRAY_SIZE(values); i++)
{
char c = in[inpos++];
if (c >= '0' && c <= '9')
values[i] = c - '0';
else if (c >= 'A' && c <= 'F')
values[i] = c - 'A' + 10;
else if (c >= 'a' && c <= 'f')
values[i] = c - 'a' + 10;
else
{
ret = grub_error (GRUB_ERR_BAD_ARGUMENT,
N_("unicode sequence with invalid character '%c'"), c);
goto err;
}
}
if (values[0] != 0 || values[1] != 0)
result[resultpos++] = values[0] << 4 | values[1];
result[resultpos++] = values[2] << 4 | values[3];
/* Offset the increment that's coming in via the loop increment. */
inpos--;
break;
}
default:
ret = grub_error (GRUB_ERR_BAD_ARGUMENT, N_("unrecognized escaped character '%c'"), in[inpos]);
goto err;
}
}
else
result[resultpos++] = in[inpos];
}
*out = result;
*outlen = resultpos;
err:
if (ret != GRUB_ERR_NONE)
grub_free (result);
return ret;
}

View File

@ -125,4 +125,16 @@ extern grub_err_t EXPORT_FUNC(grub_json_getint64) (grub_int64_t *out,
const grub_json_t *parent,
const char *key);
/*
* Unescape escaped characters and Unicode sequences in the
* given JSON-encoded string. Returns a newly allocated string
* passed back via the `out` parameter that has a length of
* `*outlen`.
*
* See https://datatracker.ietf.org/doc/html/rfc8259#section-7 for more
* information on escaping in JSON.
*/
extern grub_err_t EXPORT_FUNC(grub_json_unescape) (char **out, grub_size_t *outlen,
const char *in, grub_size_t inlen);
#endif