From be64f81eeeadb7bc8b2330f7e94dfc79f9e7421c Mon Sep 17 00:00:00 2001 From: Deomid Ryabkov Date: Sat, 24 Oct 2020 22:43:51 +0100 Subject: [PATCH] Add mg_next_query_string_entry_n() and mg_url_decode_n() Move to mg_util.h so encode and decode are next ot each other. Pull out mg_next_list_entry_n() for advanced use cases. Add unit tests. --- mongoose.c | 155 ++++++++++++++++++++++++--------------- mongoose.h | 43 +++++++---- src/.clang-format | 6 ++ src/common/str_util.c | 77 ++++++++++++-------- src/common/str_util.h | 8 ++ src/mg_http.c | 28 ------- src/mg_http.h | 14 ---- src/mg_util.c | 50 +++++++++++++ src/mg_util.h | 21 ++++++ test/.clang-format | 6 ++ test/Makefile | 4 + test/unit_test.c | 166 +++++++++++++++++++++++++++++++++++++++--- tools/README.md | 2 +- 13 files changed, 424 insertions(+), 156 deletions(-) create mode 100644 src/.clang-format create mode 100644 test/.clang-format diff --git a/mongoose.c b/mongoose.c index 93dd5fc7..ebabcc05 100644 --- a/mongoose.c +++ b/mongoose.c @@ -2116,6 +2116,43 @@ int mg_avprintf(char **buf, size_t size, const char *fmt, va_list ap) { return len; } +static struct mg_str mg_next_list_entry_n(struct mg_str list, char sep1, + struct mg_str *val1, char sep2, + struct mg_str *val2) { + if (list.len == 0) { + /* End of the list */ + list = mg_mk_str(NULL); + } else { + const char *chr = NULL; + *val1 = list; + + if ((chr = mg_strchr(*val1, sep1)) != NULL) { + /* Comma found. Store length and shift the list ptr */ + val1->len = chr - val1->p; + chr++; + list.len -= (chr - list.p); + list.p = chr; + } else { + /* This value is the last one */ + list = mg_mk_str_n(list.p + list.len, 0); + } + + if (val2 != NULL) { + /* Value has form "x=y", adjust pointers and lengths */ + /* so that val points to "x", and eq_val points to "y". */ + val2->len = 0; + val2->p = (const char *) memchr(val1->p, sep2, val1->len); + if (val2->p != NULL) { + val2->p++; /* Skip over sep2 character */ + val2->len = val1->p + val1->len - val2->p; + val1->len = (val2->p - val1->p) - 1; + } + } + } + + return list; +} + const char *mg_next_comma_list_entry(const char *, struct mg_str *, struct mg_str *) WEAK; const char *mg_next_comma_list_entry(const char *list, struct mg_str *val, @@ -2128,38 +2165,16 @@ struct mg_str mg_next_comma_list_entry_n(struct mg_str list, struct mg_str *val, struct mg_str *eq_val) WEAK; struct mg_str mg_next_comma_list_entry_n(struct mg_str list, struct mg_str *val, struct mg_str *eq_val) { - if (list.len == 0) { - /* End of the list */ - list = mg_mk_str(NULL); - } else { - const char *chr = NULL; - *val = list; + return mg_next_list_entry_n(list, ',', val, '=', eq_val); +} - if ((chr = mg_strchr(*val, ',')) != NULL) { - /* Comma found. Store length and shift the list ptr */ - val->len = chr - val->p; - chr++; - list.len -= (chr - list.p); - list.p = chr; - } else { - /* This value is the last one */ - list = mg_mk_str_n(list.p + list.len, 0); - } - - if (eq_val != NULL) { - /* Value has form "x=y", adjust pointers and lengths */ - /* so that val points to "x", and eq_val points to "y". */ - eq_val->len = 0; - eq_val->p = (const char *) memchr(val->p, '=', val->len); - if (eq_val->p != NULL) { - eq_val->p++; /* Skip over '=' character */ - eq_val->len = val->p + val->len - eq_val->p; - val->len = (eq_val->p - val->p) - 1; - } - } - } - - return list; +struct mg_str mg_next_query_string_entry_n(struct mg_str list, + struct mg_str *val, + struct mg_str *eq_val) WEAK; +struct mg_str mg_next_query_string_entry_n(struct mg_str list, + struct mg_str *val, + struct mg_str *eq_val) { + return mg_next_list_entry_n(list, '&', val, '=', eq_val); } size_t mg_match_prefix_n(const struct mg_str, const struct mg_str) WEAK; @@ -7264,34 +7279,6 @@ static void mg_http_serve_file2(struct mg_connection *nc, const char *path, #endif -int mg_url_decode(const char *src, int src_len, char *dst, int dst_len, - int is_form_url_encoded) { - int i, j, a, b; -#define HEXTOI(x) (isdigit(x) ? x - '0' : x - 'W') - - for (i = j = 0; i < src_len && j < dst_len - 1; i++, j++) { - if (src[i] == '%') { - if (i < src_len - 2 && isxdigit(*(const unsigned char *) (src + i + 1)) && - isxdigit(*(const unsigned char *) (src + i + 2))) { - a = tolower(*(const unsigned char *) (src + i + 1)); - b = tolower(*(const unsigned char *) (src + i + 2)); - dst[j] = (char) ((HEXTOI(a) << 4) | HEXTOI(b)); - i += 2; - } else { - return -1; - } - } else if (is_form_url_encoded && src[i] == '+') { - dst[j] = ' '; - } else { - dst[j] = src[i]; - } - } - - dst[j] = '\0'; /* Null-terminate the destination */ - - return i >= src_len ? j : -1; -} - int mg_get_http_var(const struct mg_str *buf, const char *name, char *dst, size_t dst_len) { const char *p, *e, *s; @@ -10662,6 +10649,56 @@ struct mg_str mg_url_encode_opt(const struct mg_str src, struct mg_str mg_url_encode(const struct mg_str src) { return mg_url_encode_opt(src, mg_mk_str("._-$,;~()/"), 0); } + +int mg_url_decode(const char *src, int src_len, char *dst, int dst_len, + int is_form_url_encoded) { + struct mg_str srcs = MG_MK_STR_N(src, (size_t) src_len); + struct mg_str dsts = MG_MK_STR_N(dst, (size_t) dst_len); + int res = mg_url_decode_n(srcs, &dsts, is_form_url_encoded); + if (res >= 0) { + if (res < dst_len) { + dst[res] = '\0'; + } else { + res = -1; /* Not enough space for NUL-temrination. */ + } + } + return res; +} + +#define HEXTOI(x) (isdigit(x) ? x - '0' : x - 'W') +int mg_url_decode_n(struct mg_str srcs, struct mg_str *dsts, + int is_form_url_encoded) { + int i, j, a, b, src_len, dst_len; + const char *src = srcs.p; + char *dst; + if (dsts == NULL) return -1; + dst = (char *) dsts->p; + src_len = (int) srcs.len; + dst_len = (int) dsts->len; + + for (i = j = 0; i < src_len && j < dst_len; i++, j++) { + if (src[i] == '%') { + if (i < src_len - 2 && isxdigit(*(const unsigned char *) (src + i + 1)) && + isxdigit(*(const unsigned char *) (src + i + 2))) { + a = tolower(*(const unsigned char *) (src + i + 1)); + b = tolower(*(const unsigned char *) (src + i + 2)); + dst[j] = (char) ((HEXTOI(a) << 4) | HEXTOI(b)); + i += 2; + } else { + break; + } + } else if (is_form_url_encoded && src[i] == '+') { + dst[j] = ' '; + } else { + dst[j] = src[i]; + } + } + dsts->len = (size_t) j; + + return i == src_len ? j : -1; +} +#undef HEXTOI + #ifdef MG_MODULE_LINES #line 1 "src/mg_mqtt.c" #endif diff --git a/mongoose.h b/mongoose.h index 84fb4421..5e18d3aa 100644 --- a/mongoose.h +++ b/mongoose.h @@ -2323,6 +2323,14 @@ const char *mg_next_comma_list_entry(const char *list, struct mg_str *val, struct mg_str mg_next_comma_list_entry_n(struct mg_str list, struct mg_str *val, struct mg_str *eq_val); +/* + * Helper for parsing query strings. + * Parses '&' and '=' entries. Does not perform unescaping. + */ +struct mg_str mg_next_query_string_entry_n(struct mg_str list, + struct mg_str *val, + struct mg_str *eq_val); + /* * Matches 0-terminated string (mg_match_prefix) or string with given length * mg_match_prefix_n against a glob pattern. Glob syntax: @@ -4325,6 +4333,27 @@ struct mg_str mg_url_encode_opt(const struct mg_str src, /* Same as `mg_url_encode_opt(src, "._-$,;~()/", 0)`. */ struct mg_str mg_url_encode(const struct mg_str src); +/* + * Decodes a URL-encoded string. + * + * Source string is specified by (`src`, `src_len`), and destination is + * (`dst`, `dst_len`). If `is_form_url_encoded` is non-zero, then + * `+` character is decoded as a blank space character. This function + * guarantees to NUL-terminate the destination. If destination is too small, + * then the source string is partially decoded and `-1` is returned. + * Otherwise, the length of the decoded string is returned, + * not counting final NUL. + */ +int mg_url_decode(const char *src, int src_len, char *dst, int dst_len, + int is_form_url_encoded); + +/* + * mg_str variant of mg_url_decode. Does not NUL-terminate dst. + * It is ok for src and dst to be the same. + */ +int mg_url_decode_n(struct mg_str src, struct mg_str *dst, + int is_form_url_encoded); + #ifdef __cplusplus } #endif /* __cplusplus */ @@ -4662,20 +4691,6 @@ void mg_printf_websocket_frame(struct mg_connection *nc, int op_and_flags, #endif /* MG_ENABLE_HTTP_WEBSOCKET */ -/* - * Decodes a URL-encoded string. - * - * Source string is specified by (`src`, `src_len`), and destination is - * (`dst`, `dst_len`). If `is_form_url_encoded` is non-zero, then - * `+` character is decoded as a blank space character. This function - * guarantees to NUL-terminate the destination. If destination is too small, - * then the source string is partially decoded and `-1` is returned. - *Otherwise, - * a length of the decoded string is returned, not counting final NUL. - */ -int mg_url_decode(const char *src, int src_len, char *dst, int dst_len, - int is_form_url_encoded); - extern void mg_hash_md5_v(size_t num_msgs, const uint8_t *msgs[], const size_t *msg_lens, uint8_t *digest); extern void mg_hash_sha1_v(size_t num_msgs, const uint8_t *msgs[], diff --git a/src/.clang-format b/src/.clang-format new file mode 100644 index 00000000..24fab55e --- /dev/null +++ b/src/.clang-format @@ -0,0 +1,6 @@ +BasedOnStyle: Google +AllowShortFunctionsOnASingleLine: false +SpaceAfterCStyleCast: true +PointerBindsToType: false +DerivePointerBinding: false +IncludeBlocks: Preserve diff --git a/src/common/str_util.c b/src/common/str_util.c index 74ab5241..cc825cba 100644 --- a/src/common/str_util.c +++ b/src/common/str_util.c @@ -413,6 +413,43 @@ int mg_avprintf(char **buf, size_t size, const char *fmt, va_list ap) { return len; } +static struct mg_str mg_next_list_entry_n(struct mg_str list, char sep1, + struct mg_str *val1, char sep2, + struct mg_str *val2) { + if (list.len == 0) { + /* End of the list */ + list = mg_mk_str(NULL); + } else { + const char *chr = NULL; + *val1 = list; + + if ((chr = mg_strchr(*val1, sep1)) != NULL) { + /* Comma found. Store length and shift the list ptr */ + val1->len = chr - val1->p; + chr++; + list.len -= (chr - list.p); + list.p = chr; + } else { + /* This value is the last one */ + list = mg_mk_str_n(list.p + list.len, 0); + } + + if (val2 != NULL) { + /* Value has form "x=y", adjust pointers and lengths */ + /* so that val points to "x", and eq_val points to "y". */ + val2->len = 0; + val2->p = (const char *) memchr(val1->p, sep2, val1->len); + if (val2->p != NULL) { + val2->p++; /* Skip over sep2 character */ + val2->len = val1->p + val1->len - val2->p; + val1->len = (val2->p - val1->p) - 1; + } + } + } + + return list; +} + const char *mg_next_comma_list_entry(const char *, struct mg_str *, struct mg_str *) WEAK; const char *mg_next_comma_list_entry(const char *list, struct mg_str *val, @@ -425,38 +462,16 @@ struct mg_str mg_next_comma_list_entry_n(struct mg_str list, struct mg_str *val, struct mg_str *eq_val) WEAK; struct mg_str mg_next_comma_list_entry_n(struct mg_str list, struct mg_str *val, struct mg_str *eq_val) { - if (list.len == 0) { - /* End of the list */ - list = mg_mk_str(NULL); - } else { - const char *chr = NULL; - *val = list; + return mg_next_list_entry_n(list, ',', val, '=', eq_val); +} - if ((chr = mg_strchr(*val, ',')) != NULL) { - /* Comma found. Store length and shift the list ptr */ - val->len = chr - val->p; - chr++; - list.len -= (chr - list.p); - list.p = chr; - } else { - /* This value is the last one */ - list = mg_mk_str_n(list.p + list.len, 0); - } - - if (eq_val != NULL) { - /* Value has form "x=y", adjust pointers and lengths */ - /* so that val points to "x", and eq_val points to "y". */ - eq_val->len = 0; - eq_val->p = (const char *) memchr(val->p, '=', val->len); - if (eq_val->p != NULL) { - eq_val->p++; /* Skip over '=' character */ - eq_val->len = val->p + val->len - eq_val->p; - val->len = (eq_val->p - val->p) - 1; - } - } - } - - return list; +struct mg_str mg_next_query_string_entry_n(struct mg_str list, + struct mg_str *val, + struct mg_str *eq_val) WEAK; +struct mg_str mg_next_query_string_entry_n(struct mg_str list, + struct mg_str *val, + struct mg_str *eq_val) { + return mg_next_list_entry_n(list, '&', val, '=', eq_val); } size_t mg_match_prefix_n(const struct mg_str, const struct mg_str) WEAK; diff --git a/src/common/str_util.h b/src/common/str_util.h index e023aa62..08f8d0f8 100644 --- a/src/common/str_util.h +++ b/src/common/str_util.h @@ -148,6 +148,14 @@ const char *mg_next_comma_list_entry(const char *list, struct mg_str *val, struct mg_str mg_next_comma_list_entry_n(struct mg_str list, struct mg_str *val, struct mg_str *eq_val); +/* + * Helper for parsing query strings. + * Parses '&' and '=' entries. Does not perform unescaping. + */ +struct mg_str mg_next_query_string_entry_n(struct mg_str list, + struct mg_str *val, + struct mg_str *eq_val); + /* * Matches 0-terminated string (mg_match_prefix) or string with given length * mg_match_prefix_n against a glob pattern. Glob syntax: diff --git a/src/mg_http.c b/src/mg_http.c index dd2cae3c..d16225f5 100644 --- a/src/mg_http.c +++ b/src/mg_http.c @@ -1624,34 +1624,6 @@ static void mg_http_serve_file2(struct mg_connection *nc, const char *path, #endif -int mg_url_decode(const char *src, int src_len, char *dst, int dst_len, - int is_form_url_encoded) { - int i, j, a, b; -#define HEXTOI(x) (isdigit(x) ? x - '0' : x - 'W') - - for (i = j = 0; i < src_len && j < dst_len - 1; i++, j++) { - if (src[i] == '%') { - if (i < src_len - 2 && isxdigit(*(const unsigned char *) (src + i + 1)) && - isxdigit(*(const unsigned char *) (src + i + 2))) { - a = tolower(*(const unsigned char *) (src + i + 1)); - b = tolower(*(const unsigned char *) (src + i + 2)); - dst[j] = (char) ((HEXTOI(a) << 4) | HEXTOI(b)); - i += 2; - } else { - return -1; - } - } else if (is_form_url_encoded && src[i] == '+') { - dst[j] = ' '; - } else { - dst[j] = src[i]; - } - } - - dst[j] = '\0'; /* Null-terminate the destination */ - - return i >= src_len ? j : -1; -} - int mg_get_http_var(const struct mg_str *buf, const char *name, char *dst, size_t dst_len) { const char *p, *e, *s; diff --git a/src/mg_http.h b/src/mg_http.h index 45797df3..14af04b5 100644 --- a/src/mg_http.h +++ b/src/mg_http.h @@ -329,20 +329,6 @@ void mg_printf_websocket_frame(struct mg_connection *nc, int op_and_flags, #endif /* MG_ENABLE_HTTP_WEBSOCKET */ -/* - * Decodes a URL-encoded string. - * - * Source string is specified by (`src`, `src_len`), and destination is - * (`dst`, `dst_len`). If `is_form_url_encoded` is non-zero, then - * `+` character is decoded as a blank space character. This function - * guarantees to NUL-terminate the destination. If destination is too small, - * then the source string is partially decoded and `-1` is returned. - *Otherwise, - * a length of the decoded string is returned, not counting final NUL. - */ -int mg_url_decode(const char *src, int src_len, char *dst, int dst_len, - int is_form_url_encoded); - extern void mg_hash_md5_v(size_t num_msgs, const uint8_t *msgs[], const size_t *msg_lens, uint8_t *digest); extern void mg_hash_sha1_v(size_t num_msgs, const uint8_t *msgs[], diff --git a/src/mg_util.c b/src/mg_util.c index 69753996..61033cf3 100644 --- a/src/mg_util.c +++ b/src/mg_util.c @@ -338,3 +338,53 @@ struct mg_str mg_url_encode_opt(const struct mg_str src, struct mg_str mg_url_encode(const struct mg_str src) { return mg_url_encode_opt(src, mg_mk_str("._-$,;~()/"), 0); } + +int mg_url_decode(const char *src, int src_len, char *dst, int dst_len, + int is_form_url_encoded) { + struct mg_str srcs = MG_MK_STR_N(src, (size_t) src_len); + struct mg_str dsts = MG_MK_STR_N(dst, (size_t) dst_len); + int res = mg_url_decode_n(srcs, &dsts, is_form_url_encoded); + if (res >= 0) { + if (res < dst_len) { + dst[res] = '\0'; + } else { + res = -1; /* Not enough space for NUL-temrination. */ + } + } + return res; +} + +#define HEXTOI(x) (isdigit(x) ? x - '0' : x - 'W') +int mg_url_decode_n(struct mg_str srcs, struct mg_str *dsts, + int is_form_url_encoded) { + int i, j, a, b, src_len, dst_len; + const char *src = srcs.p; + char *dst; + if (dsts == NULL) return -1; + dst = (char *) dsts->p; + src_len = (int) srcs.len; + dst_len = (int) dsts->len; + + for (i = j = 0; i < src_len && j < dst_len; i++, j++) { + if (src[i] == '%') { + if (i < src_len - 2 && isxdigit(*(const unsigned char *) (src + i + 1)) && + isxdigit(*(const unsigned char *) (src + i + 2))) { + a = tolower(*(const unsigned char *) (src + i + 1)); + b = tolower(*(const unsigned char *) (src + i + 2)); + dst[j] = (char) ((HEXTOI(a) << 4) | HEXTOI(b)); + i += 2; + } else { + break; + } + } else if (is_form_url_encoded && src[i] == '+') { + dst[j] = ' '; + } else { + dst[j] = src[i]; + } + } + dsts->len = (size_t) j; + + return i == src_len ? j : -1; +} +#undef HEXTOI + diff --git a/src/mg_util.h b/src/mg_util.h index 4122aed3..9772480a 100644 --- a/src/mg_util.h +++ b/src/mg_util.h @@ -205,6 +205,27 @@ struct mg_str mg_url_encode_opt(const struct mg_str src, /* Same as `mg_url_encode_opt(src, "._-$,;~()/", 0)`. */ struct mg_str mg_url_encode(const struct mg_str src); +/* + * Decodes a URL-encoded string. + * + * Source string is specified by (`src`, `src_len`), and destination is + * (`dst`, `dst_len`). If `is_form_url_encoded` is non-zero, then + * `+` character is decoded as a blank space character. This function + * guarantees to NUL-terminate the destination. If destination is too small, + * then the source string is partially decoded and `-1` is returned. + * Otherwise, the length of the decoded string is returned, + * not counting final NUL. + */ +int mg_url_decode(const char *src, int src_len, char *dst, int dst_len, + int is_form_url_encoded); + +/* + * mg_str variant of mg_url_decode. Does not NUL-terminate dst. + * It is ok for src and dst to be the same. + */ +int mg_url_decode_n(struct mg_str src, struct mg_str *dst, + int is_form_url_encoded); + #ifdef __cplusplus } #endif /* __cplusplus */ diff --git a/test/.clang-format b/test/.clang-format new file mode 100644 index 00000000..24fab55e --- /dev/null +++ b/test/.clang-format @@ -0,0 +1,6 @@ +BasedOnStyle: Google +AllowShortFunctionsOnASingleLine: false +SpaceAfterCStyleCast: true +PointerBindsToType: false +DerivePointerBinding: false +IncludeBlocks: Preserve diff --git a/test/Makefile b/test/Makefile index 9bbdb900..52dab3d9 100644 --- a/test/Makefile +++ b/test/Makefile @@ -112,3 +112,7 @@ fuzz: # docker run -v $(CURDIR)/../..:/cesanta -t -i --entrypoint=/bin/bash cesanta/mongoose_test docker: docker run --rm -v $(CURDIR)/../..:/cesanta cesanta/mongoose_test + +amalgam: + cd .. && tools/amalgam.py --prefix=MG --public-header=mongoose.h --license=LICENSE `cat src/mongoose.c.manifest` > mongoose.c + cd .. && tools/amalgam.py --prefix=MG --license=LICENSE `cat src/mongoose.h.manifest` > mongoose.h diff --git a/test/unit_test.c b/test/unit_test.c index bc84c084..2adf4509 100644 --- a/test/unit_test.c +++ b/test/unit_test.c @@ -19,8 +19,8 @@ #include "mongoose.h" -#include "common/cs_md5.h" #include "../src/mg_internal.h" +#include "common/cs_md5.h" #include "test_main.h" #include "test_util.h" @@ -856,6 +856,106 @@ static const char *test_mg_normalize_uri_path(void) { return NULL; } +static const char *test_mg_next_list_entry(void) { + struct mg_str val1, val2; + { + struct mg_str l = MG_NULL_STR; + l = mg_next_comma_list_entry_n(l, &val1, &val2); + ASSERT_TRUE(l.p == NULL); + ASSERT_EQ(l.len, 0); + } + { + struct mg_str l = MG_MK_STR("a"); + l = mg_next_comma_list_entry_n(l, &val1, &val2); + ASSERT_TRUE(l.p != NULL); + ASSERT_EQ(l.len, 0); + ASSERT_MG_STREQ(val1, "a"); + ASSERT_MG_STREQ(val2, ""); + l = mg_next_comma_list_entry_n(l, &val1, &val2); + ASSERT_TRUE(l.p == NULL); + ASSERT_EQ(l.len, 0); + } + { + struct mg_str l = MG_MK_STR("a=1"); + l = mg_next_comma_list_entry_n(l, &val1, &val2); + ASSERT_TRUE(l.p != NULL); + ASSERT_EQ(l.len, 0); + ASSERT_MG_STREQ(val1, "a"); + ASSERT_MG_STREQ(val2, "1"); + l = mg_next_comma_list_entry_n(l, &val1, &val2); + ASSERT_TRUE(l.p == NULL); + ASSERT_EQ(l.len, 0); + } + { + struct mg_str l = MG_MK_STR("a="); + l = mg_next_comma_list_entry_n(l, &val1, &val2); + ASSERT_TRUE(l.p != NULL); + ASSERT_EQ(l.len, 0); + ASSERT_MG_STREQ(val1, "a"); + ASSERT_MG_STREQ(val2, ""); + l = mg_next_comma_list_entry_n(l, &val1, &val2); + ASSERT_TRUE(l.p == NULL); + ASSERT_EQ(l.len, 0); + } + { + struct mg_str l = MG_MK_STR("a=,"); + l = mg_next_comma_list_entry_n(l, &val1, &val2); + ASSERT_TRUE(l.p != NULL); + ASSERT_EQ(l.len, 0); + ASSERT_MG_STREQ(val1, "a"); + ASSERT_MG_STREQ(val2, ""); + l = mg_next_comma_list_entry_n(l, &val1, &val2); + ASSERT_TRUE(l.p == NULL); + ASSERT_EQ(l.len, 0); + } + { + struct mg_str l = MG_MK_STR("a=123,b=456"); + l = mg_next_comma_list_entry_n(l, &val1, &val2); + ASSERT_MG_STREQ(l, "b=456"); + ASSERT_MG_STREQ(val1, "a"); + ASSERT_MG_STREQ(val2, "123"); + l = mg_next_comma_list_entry_n(l, &val1, &val2); + ASSERT_TRUE(l.p != NULL); + ASSERT_EQ(l.len, 0); + ASSERT_MG_STREQ(val1, "b"); + ASSERT_MG_STREQ(val2, "456"); + l = mg_next_comma_list_entry_n(l, &val1, &val2); + ASSERT_TRUE(l.p == NULL); + ASSERT_EQ(l.len, 0); + } + { + struct mg_str l = MG_MK_STR("a,b=456"); + l = mg_next_comma_list_entry_n(l, &val1, &val2); + ASSERT_MG_STREQ(l, "b=456"); + ASSERT_MG_STREQ(val1, "a"); + ASSERT_MG_STREQ(val2, ""); + l = mg_next_comma_list_entry_n(l, &val1, &val2); + ASSERT_TRUE(l.p != NULL); + ASSERT_EQ(l.len, 0); + ASSERT_MG_STREQ(val1, "b"); + ASSERT_MG_STREQ(val2, "456"); + l = mg_next_comma_list_entry_n(l, &val1, &val2); + ASSERT_TRUE(l.p == NULL); + ASSERT_EQ(l.len, 0); + } + { + struct mg_str l = MG_MK_STR("a,b&c=4+5%206"); + l = mg_next_query_string_entry_n(l, &val1, &val2); + ASSERT_MG_STREQ(l, "c=4+5%206"); + ASSERT_MG_STREQ(val1, "a,b"); + ASSERT_MG_STREQ(val2, ""); + l = mg_next_query_string_entry_n(l, &val1, &val2); + ASSERT_TRUE(l.p != NULL); + ASSERT_EQ(l.len, 0); + ASSERT_MG_STREQ(val1, "c"); + ASSERT_MG_STREQ(val2, "4+5%206"); + l = mg_next_query_string_entry_n(l, &val1, &val2); + ASSERT_TRUE(l.p == NULL); + ASSERT_EQ(l.len, 0); + } + return NULL; +} + #define CHECK_U2LP(u, exp_rv, exp_lp, exp_rem) \ do { \ int rv; \ @@ -931,21 +1031,25 @@ static const char *test_mg_uri_to_local_path(void) { return NULL; } -static const char *test_mg_url_encode(void) { - const struct mg_str encode_me = - MG_MK_STR("I'm a.little_tea-pot,here's$my;spout~oink(oink)oink/!"); +static const char *test_mg_url_encode_decode(void) { +#define ENCODE_ME "I'm a.little_tea-po+,here's$my;spout~oink(oink)oink/!" + struct mg_str encode_me = MG_MK_STR(ENCODE_ME "XXX"); + encode_me.len -= 3; /* Not nul-terminated on purpose */ { struct mg_str encoded = mg_url_encode(encode_me); ASSERT_MG_STREQ( encoded, - "I%27m%20a.little_tea-pot,here%27s$my;spout~oink(oink)oink/%21"); + "I%27m%20a.little_tea-po%2b,here%27s$my;spout~oink(oink)oink/%21"); + ASSERT_EQ(mg_url_decode_n(encoded, &encoded, 1), encode_me.len); + ASSERT_MG_STREQ(encoded, ENCODE_ME); free((void *) encoded.p); } { struct mg_str encoded = mg_url_encode_opt(encode_me, mg_mk_str(NULL), 0); ASSERT_MG_STREQ(encoded, - "I%27m%20a%2elittle%5ftea%2dpot%2chere%27s%24my%3bspout%" + "I%27m%20a%2elittle%5ftea%2dpo%2b%2chere%27s%24my%3bspout%" "7eoink%28oink%29oink%2f%21"); + ASSERT_EQ(mg_url_decode_n(encoded, &encoded, 0), encode_me.len); free((void *) encoded.p); } { @@ -953,8 +1057,10 @@ static const char *test_mg_url_encode(void) { MG_URL_ENCODE_F_UPPERCASE_HEX); ASSERT_MG_STREQ(encoded, "I%27m " - "a%2Elittle%5Ftea%2Dpot%2Chere%27s%24my%3Bspout%7Eoink%" + "a%2Elittle%5Ftea%2Dpo%2B%2Chere%27s%24my%3Bspout%7Eoink%" "28oink%29oink/!"); + ASSERT_EQ(mg_url_decode_n(encoded, &encoded, 0), encode_me.len); + ASSERT_MG_STREQ(encoded, ENCODE_ME); free((void *) encoded.p); } { @@ -962,10 +1068,51 @@ static const char *test_mg_url_encode(void) { encode_me, mg_mk_str("/!"), MG_URL_ENCODE_F_SPACE_AS_PLUS | MG_URL_ENCODE_F_UPPERCASE_HEX); ASSERT_MG_STREQ(encoded, - "I%27m+a%2Elittle%5Ftea%2Dpot%2Chere%27s%24my%3Bspout%" + "I%27m+a%2Elittle%5Ftea%2Dpo%2B%2Chere%27s%24my%3Bspout%" "7Eoink%28oink%29oink/!"); + ASSERT_EQ(mg_url_decode_n(encoded, &encoded, 1), encode_me.len); + ASSERT_MG_STREQ(encoded, ENCODE_ME); free((void *) encoded.p); } + { + struct mg_str in = MG_MK_STR("a%20b%20c"); + char outbuf[6] = {'X', 'X', 'X', 'X', 'X', 'X'}; + struct mg_str out = MG_MK_STR_N(outbuf, sizeof(outbuf)), out1 = out; + ASSERT_EQ(mg_url_decode_n(in, &out, 0), 5); + ASSERT_MG_STREQ(out, "a b c"); + ASSERT_MG_STREQ(out1, "a b cX"); + } + { + struct mg_str in = MG_MK_STR("a%20b%20c"); + char outbuf[6] = {'X', 'X', 'X', 'X', 'X', 'X'}; + ASSERT_EQ(mg_url_decode(in.p, (int) in.len, outbuf, sizeof(outbuf), 0), 5); + /* NUL-terminated for her pleasure. */ + ASSERT_MG_STREQ(mg_mk_str(outbuf), "a b c"); + } + { + struct mg_str in = MG_MK_STR("a%20b%20c"); + char outbuf[6] = {'X', 'X', 'X', 'X', 'X', 'X'}; + struct mg_str out = MG_MK_STR_N(outbuf, 4); + ASSERT_EQ(mg_url_decode_n(in, &out, 0), -1); + ASSERT_MG_STREQ(out, "a b "); + ASSERT_MG_STREQ(mg_mk_str_n(outbuf, sizeof(outbuf)), "a b XX"); + } + { + struct mg_str in = MG_MK_STR("a%20b%20c"); + char outbuf[6] = {'X', 'X', 'X', 'X', 'X', 'X'}; + ASSERT_EQ(mg_url_decode(in.p, (int) in.len, outbuf, 5, 0), -1); + /* Not enough space to NUL-terminate. */ + struct mg_str out1 = MG_MK_STR_N(outbuf, sizeof(outbuf)); + ASSERT_MG_STREQ(out1, "a b cX"); + } + { + struct mg_str in = MG_MK_STR("a%20b%YYc"); + char outbuf[6] = {'X', 'X', 'X', 'X', 'X', 'X'}; + struct mg_str out = MG_MK_STR_N(outbuf, sizeof(outbuf)), out1 = out; + ASSERT_EQ(mg_url_decode_n(in, &out, 0), -1); + ASSERT_MG_STREQ(out, "a b"); + ASSERT_MG_STREQ(out1, "a bXXX"); + } return NULL; } @@ -5788,8 +5935,9 @@ const char *tests_run(const char *filter) { RUN_TEST(test_assemble_uri); RUN_TEST(test_parse_address); RUN_TEST(test_mg_normalize_uri_path); + RUN_TEST(test_mg_next_list_entry); RUN_TEST(test_mg_uri_to_local_path); - RUN_TEST(test_mg_url_encode); + RUN_TEST(test_mg_url_encode_decode); RUN_TEST(test_check_ip_acl); RUN_TEST(test_connect_opts); RUN_TEST(test_connect_opts_error_string); diff --git a/tools/README.md b/tools/README.md index 6e1b14e4..c4adcb53 100644 --- a/tools/README.md +++ b/tools/README.md @@ -21,4 +21,4 @@ $ tools/amalgam.py --prefix=MG --public-header=mongoose.h $(cat mongoose.c.manif The same applies to `mongoose.h`, except `--public-header` should be omitted during amalgamation. -`tools/amalgam.sh` can be used to assemble `mongoose.c` and `mongoose.h`. +`tools/amalgam.py` can be used to assemble `mongoose.c` and `mongoose.h`.