diff --git a/include/libwebsockets/lws-misc.h b/include/libwebsockets/lws-misc.h index f71158ab4..e9185504f 100644 --- a/include/libwebsockets/lws-misc.h +++ b/include/libwebsockets/lws-misc.h @@ -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 * diff --git a/lib/core/libwebsockets.c b/lib/core/libwebsockets.c index adecc1c38..d034fe79d 100644 --- a/lib/core/libwebsockets.c +++ b/lib/core/libwebsockets.c @@ -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 * diff --git a/minimal-examples/api-tests/api-test-lws_tokenize/main.c b/minimal-examples/api-tests/api-test-lws_tokenize/main.c index 7682ede1f..e6d26d63c 100644 --- a/minimal-examples/api-tests/api-test-lws_tokenize/main.c +++ b/minimal-examples/api-tests/api-test-lws_tokenize/main.c @@ -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++) {