1
0
Fork 0
mirror of https://github.com/warmcat/libwebsockets.git synced 2025-03-09 00:00:04 +01:00

lws_json_simple_find and lws_nstrstr

String helpers for scanning non-NUL-delimited strings safely,
and very cheap simple string match based JSON parse for cases
that make sense for it... for more complex cases, do a full
JSON parse.
This commit is contained in:
Andy Green 2020-06-19 11:10:56 +01:00
parent d4a7c7134a
commit 5a937fa830
3 changed files with 240 additions and 0 deletions

View file

@ -180,6 +180,68 @@ lws_strncpy(char *dest, const char *src, size_t size);
lws_strncpy(dest, src, (size_t)(size1 + 1) < (size_t)(destsize) ? \
(size_t)(size1 + 1) : (size_t)(destsize))
/**
* lws_nstrstr(): like strstr for length-based strings without terminating NUL
*
* \param buf: the string to search
* \param len: the length of the string to search
* \param name: the substring to search for
* \param nl: the length of name
*
* Returns NULL if \p name is not present in \p buf. Otherwise returns the
* address of the first instance of \p name in \p buf.
*
* Neither buf nor name need to be NUL-terminated.
*/
LWS_VISIBLE LWS_EXTERN const char *
lws_nstrstr(const char *buf, size_t len, const char *name, size_t nl);
/**
* lws_json_simple_find(): dumb JSON string parser
*
* \param buf: the JSON to search
* \param len: the length of the JSON to search
* \param name: the name field to search the JSON for, eg, "\"myname\":"
* \param alen: set to the length of the argument part if non-NULL return
*
* Either returns NULL if \p name is not present in buf, or returns a pointer
* to the argument body of the first instance of \p name, and sets *alen to the
* length of the argument body.
*
* This can cheaply handle fishing out, eg, myarg from {"myname": "myarg"} by
* searching for "\"myname\":". It will return a pointer to myarg and set *alen
* to 5. It equally handles args like "myname": true, or "myname":false, and
* null or numbers are all returned as delimited strings.
*
* Anything more complicated like the value is a subobject or array, you should
* parse it using a full parser like lejp. This is suitable is the JSON is
* and will remain short and simple, and contains well-known names amongst other
* extensible JSON members.
*/
LWS_VISIBLE LWS_EXTERN const char *
lws_json_simple_find(const char *buf, size_t len, const char *name, size_t *alen);
/**
* lws_json_simple_strcmp(): dumb JSON string comparison
*
* \param buf: the JSON to search
* \param len: the length of the JSON to search
* \param name: the name field to search the JSON for, eg, "\"myname\":"
* \param comp: return a strcmp of this and the discovered argument
*
* Helper that combines lws_json_simple_find() with strcmp() if it was found.
* If the \p name was not found, returns -1. Otherwise returns a strcmp()
* between what was found and \p comp, ie, return 0 if they match or something
* else if they don't.
*
* If the JSON is relatively simple and you want to target constrained
* devices, this can be a good choice. If the JSON may be complex, you
* should use a full JSON parser.
*/
LWS_VISIBLE LWS_EXTERN int
lws_json_simple_strcmp(const char *buf, size_t len, const char *name, const char *comp);
/**
* lws_hex_to_byte_array(): convert hex string like 0123456789ab into byte data
*

View file

@ -345,6 +345,117 @@ lws_strdup(const char *s)
return d;
}
const char *
lws_nstrstr(const char *buf, size_t len, const char *name, size_t nl)
{
const char *end = buf + len - nl + 1;
size_t n;
if (nl > len)
/* it cannot be found if the needle is longer than the haystack */
return NULL;
while (buf < end) {
if (*buf != name[0]) {
buf++;
continue;
}
if (nl == 1)
/* single char match, we are done */
return buf;
if (buf[nl - 1] == name[nl - 1]) {
/*
* This is looking interesting then... the first
* and last chars match, let's check the insides
*/
n = 1;
while (n < nl && buf[n] == name[n])
n++;
if (n == nl)
/* it's a hit */
return buf;
}
buf++;
}
return NULL;
}
/*
* name wants to be something like "\"myname\":"
*/
const char *
lws_json_simple_find(const char *buf, size_t len, const char *name, size_t *alen)
{
size_t nl = strlen(name);
const char *np = lws_nstrstr(buf, len, name, nl),
*end = buf + len, *as;
int qu = 0;
if (!np)
return NULL;
np += nl;
while (np < end && (*np == ' ' || *np == '\t'))
np++;
if (np >= end - 1)
return NULL;
/*
* The arg could be lots of things after "name": with JSON, commonly a
* string like "mystring", true, false, null, [...] or {...} ... we want
* to handle common, simple cases cheaply with this; the user can choose
* a full JSON parser like lejp if it's complicated. So if no opening
* quote, return until a terminator like , ] }. If there's an opening
* quote, return until closing quote, handling escaped quotes.
*/
if (*np == '\"') {
qu = 1;
np++;
}
as = np;
while (np < end &&
(!qu || *np != '\"') && /* end quote is EOT if quoted */
(qu || (*np != '}' && *np != ']' && *np != ',')) /* delimiters */
) {
if (qu && *np == '\\') /* skip next char if quoted escape */
np++;
np++;
}
if (np == end)
return NULL;
*alen = lws_ptr_diff(np, as);
return as;
}
int
lws_json_simple_strcmp(const char *buf, size_t len, const char *name,
const char *comp)
{
size_t al;
const char *hit = lws_json_simple_find(buf, len, name, &al);
if (!hit)
return -1;
if (al != strlen(comp))
return -1;
return strncmp(hit, comp, al);
}
static const char *hex = "0123456789ABCDEF";
const char *

View file

@ -417,6 +417,73 @@ int main(int argc, const char **argv)
return 1;
}
/* sanity check lws_nstrstr() */
{
static const char *t1 = "abc123456";
const char *mcp;
mcp = lws_nstrstr(t1, strlen(t1), "abc", 3);
if (mcp != t1) {
lwsl_err("%s: lws_nstrstr 1 failed\n", __func__);
return 1;
}
mcp = lws_nstrstr(t1, strlen(t1), "def", 3);
if (mcp != NULL) {
lwsl_err("%s: lws_nstrstr 2 failed\n", __func__);
return 1;
}
mcp = lws_nstrstr(t1, strlen(t1), "456", 3);
if (mcp != t1 + 6) {
lwsl_err("%s: lws_nstrstr 3 failed: %p\n", __func__, mcp);
return 1;
}
mcp = lws_nstrstr(t1, strlen(t1), "1", 1);
if (mcp != t1 + 3) {
lwsl_err("%s: lws_nstrstr 4 failed\n", __func__);
return 1;
}
mcp = lws_nstrstr(t1, strlen(t1), "abc1234567", 10);
if (mcp != NULL) {
lwsl_err("%s: lws_nstrstr 5 failed\n", __func__);
return 1;
}
}
/* sanity check lws_json_simple_find() */
{
static const char *t1 = "{\"myname1\":true,"
"\"myname2\":\"string\", "
"\"myname3\": 123}";
size_t alen;
const char *mcp;
mcp = lws_json_simple_find(t1, strlen(t1), "\"myname1\":", &alen);
if (mcp != t1 + 11 || alen != 4) {
lwsl_err("%s: lws_json_simple_find 1 failed: (%d) %s\n", __func__, (int)alen, mcp);
return 1;
}
mcp = lws_json_simple_find(t1, strlen(t1), "\"myname2\":", &alen);
if (mcp != t1 + 27 || alen != 6) {
lwsl_err("%s: lws_json_simple_find 2 failed\n", __func__);
return 1;
}
mcp = lws_json_simple_find(t1, strlen(t1), "\"myname3\":", &alen);
if (mcp != t1 + 47 || alen != 3) {
lwsl_err("%s: lws_json_simple_find 3 failed\n", __func__);
return 1;
}
mcp = lws_json_simple_find(t1, strlen(t1), "\"nope\":", &alen);
if (mcp != NULL) {
lwsl_err("%s: lws_json_simple_find 4 failed\n", __func__);
return 1;
}
}
p = lws_cmdline_option(argc, argv, "-s");
for (n = 0; n < (int)LWS_ARRAY_SIZE(tests); n++) {