From e89be2e944ac1dd4d95c65d32b1194fab33ae54d Mon Sep 17 00:00:00 2001
From: Deomid Ryabkov <rojer@cesanta.com>
Date: Tue, 10 Apr 2018 02:34:35 +0100
Subject: [PATCH] Add mg_url_encode_opt()

CL: Add `mg_url_encode_opt()` - a parametrized version of `mg_url_encode()`

PUBLISHED_FROM=17fa57a7a5325b51b6e3aef3855eac4e82c35782
---
 docs/c-api/mg_util.h/mg_url_encode.md |  5 +---
 mongoose.c                            | 16 +++++++++---
 mongoose.h                            | 17 ++++++++++++-
 src/mg_util.c                         | 16 +++++++++---
 src/mg_util.h                         |  9 ++++++-
 test/unit_test.c                      | 36 ++++++++++++++++++++++++---
 6 files changed, 81 insertions(+), 18 deletions(-)

diff --git a/docs/c-api/mg_util.h/mg_url_encode.md b/docs/c-api/mg_util.h/mg_url_encode.md
index 105ce9473..176ac5f7a 100644
--- a/docs/c-api/mg_util.h/mg_url_encode.md
+++ b/docs/c-api/mg_util.h/mg_url_encode.md
@@ -6,8 +6,5 @@ signature: |
   struct mg_str mg_url_encode(const struct mg_str src);
 ---
 
-URL-escape the specified string.
-All non-printable characters are escaped, plus `._-$,;~()/`.
-Input need not be NUL-terminated, but the returned string is.
-Returned string is heap-allocated and must be free()'d. 
+Same as `mg_url_encode_opt(src, "._-$,;~()/", 0)`. 
 
diff --git a/mongoose.c b/mongoose.c
index 534009b74..1eb690e71 100644
--- a/mongoose.c
+++ b/mongoose.c
@@ -10444,17 +10444,21 @@ void mg_basic_auth_header(const struct mg_str user, const struct mg_str pass,
   mbuf_append(buf, header_suffix, strlen(header_suffix));
 }
 
-struct mg_str mg_url_encode(const struct mg_str src) {
-  static const char *dont_escape = "._-$,;~()/";
-  static const char *hex = "0123456789abcdef";
+struct mg_str mg_url_encode_opt(const struct mg_str src,
+                                const struct mg_str safe, unsigned int flags) {
+  const char *hex =
+      (flags & MG_URL_ENCODE_F_UPPERCASE_HEX ? "0123456789ABCDEF"
+                                             : "0123456789abcdef");
   size_t i = 0;
   struct mbuf mb;
   mbuf_init(&mb, src.len);
 
   for (i = 0; i < src.len; i++) {
     const unsigned char c = *((const unsigned char *) src.p + i);
-    if (isalnum(c) || strchr(dont_escape, c) != NULL) {
+    if (isalnum(c) || mg_strchr(safe, c) != NULL) {
       mbuf_append(&mb, &c, 1);
+    } else if (c == ' ' && (flags & MG_URL_ENCODE_F_SPACE_AS_PLUS)) {
+      mbuf_append(&mb, "+", 1);
     } else {
       mbuf_append(&mb, "%", 1);
       mbuf_append(&mb, &hex[c >> 4], 1);
@@ -10465,6 +10469,10 @@ struct mg_str mg_url_encode(const struct mg_str src) {
   mbuf_trim(&mb);
   return mg_mk_str_n(mb.buf, mb.len - 1);
 }
+
+struct mg_str mg_url_encode(const struct mg_str src) {
+  return mg_url_encode_opt(src, mg_mk_str("._-$,;~()/"), 0);
+}
 #ifdef MG_MODULE_LINES
 #line 1 "mongoose/src/mg_mqtt.c"
 #endif
diff --git a/mongoose.h b/mongoose.h
index 080642c3b..a02d96466 100644
--- a/mongoose.h
+++ b/mongoose.h
@@ -2314,6 +2314,14 @@ void cs_base64_finish(struct cs_base64_ctx *ctx);
 
 void cs_base64_encode(const unsigned char *src, int src_len, char *dst);
 void cs_fprint_base64(FILE *f, const unsigned char *src, int src_len);
+
+/*
+ * Decodes a base64 string `s` length `len` into `dst`.
+ * `dst` must have enough space to hold the result.
+ * `*dec_len` will contain the resulting length of the string in `dst`
+ * while return value will return number of processed bytes in `src`.
+ * Return value == len indicates successful processing of all the data.
+ */
 int cs_base64_decode(const unsigned char *s, int len, char *dst, int *dec_len);
 
 #ifdef __cplusplus
@@ -4501,10 +4509,17 @@ void mg_basic_auth_header(const struct mg_str user, const struct mg_str pass,
 
 /*
  * URL-escape the specified string.
- * All non-printable characters are escaped, plus `._-$,;~()/`.
+ * All characters acept letters, numbers and characters listed in
+ * `safe` are escaped. If `hex_upper`is true, `A-F` are used for hex digits.
  * Input need not be NUL-terminated, but the returned string is.
  * Returned string is heap-allocated and must be free()'d.
  */
+#define MG_URL_ENCODE_F_SPACE_AS_PLUS (1 << 0)
+#define MG_URL_ENCODE_F_UPPERCASE_HEX (1 << 1)
+struct mg_str mg_url_encode_opt(const struct mg_str src,
+                                const struct mg_str safe, unsigned int flags);
+
+/* Same as `mg_url_encode_opt(src, "._-$,;~()/", 0)`. */
 struct mg_str mg_url_encode(const struct mg_str src);
 
 #ifdef __cplusplus
diff --git a/src/mg_util.c b/src/mg_util.c
index 95246a8db..cfbd87e5a 100644
--- a/src/mg_util.c
+++ b/src/mg_util.c
@@ -313,17 +313,21 @@ void mg_basic_auth_header(const struct mg_str user, const struct mg_str pass,
   mbuf_append(buf, header_suffix, strlen(header_suffix));
 }
 
-struct mg_str mg_url_encode(const struct mg_str src) {
-  static const char *dont_escape = "._-$,;~()/";
-  static const char *hex = "0123456789abcdef";
+struct mg_str mg_url_encode_opt(const struct mg_str src,
+                                const struct mg_str safe, unsigned int flags) {
+  const char *hex =
+      (flags & MG_URL_ENCODE_F_UPPERCASE_HEX ? "0123456789ABCDEF"
+                                             : "0123456789abcdef");
   size_t i = 0;
   struct mbuf mb;
   mbuf_init(&mb, src.len);
 
   for (i = 0; i < src.len; i++) {
     const unsigned char c = *((const unsigned char *) src.p + i);
-    if (isalnum(c) || strchr(dont_escape, c) != NULL) {
+    if (isalnum(c) || mg_strchr(safe, c) != NULL) {
       mbuf_append(&mb, &c, 1);
+    } else if (c == ' ' && (flags & MG_URL_ENCODE_F_SPACE_AS_PLUS)) {
+      mbuf_append(&mb, "+", 1);
     } else {
       mbuf_append(&mb, "%", 1);
       mbuf_append(&mb, &hex[c >> 4], 1);
@@ -334,3 +338,7 @@ struct mg_str mg_url_encode(const struct mg_str src) {
   mbuf_trim(&mb);
   return mg_mk_str_n(mb.buf, mb.len - 1);
 }
+
+struct mg_str mg_url_encode(const struct mg_str src) {
+  return mg_url_encode_opt(src, mg_mk_str("._-$,;~()/"), 0);
+}
diff --git a/src/mg_util.h b/src/mg_util.h
index e6f2e4813..8ddeb770f 100644
--- a/src/mg_util.h
+++ b/src/mg_util.h
@@ -196,10 +196,17 @@ void mg_basic_auth_header(const struct mg_str user, const struct mg_str pass,
 
 /*
  * URL-escape the specified string.
- * All non-printable characters are escaped, plus `._-$,;~()/`.
+ * All characters acept letters, numbers and characters listed in
+ * `safe` are escaped. If `hex_upper`is true, `A-F` are used for hex digits.
  * Input need not be NUL-terminated, but the returned string is.
  * Returned string is heap-allocated and must be free()'d.
  */
+#define MG_URL_ENCODE_F_SPACE_AS_PLUS (1 << 0)
+#define MG_URL_ENCODE_F_UPPERCASE_HEX (1 << 1)
+struct mg_str mg_url_encode_opt(const struct mg_str src,
+                                const struct mg_str safe, unsigned int flags);
+
+/* Same as `mg_url_encode_opt(src, "._-$,;~()/", 0)`. */
 struct mg_str mg_url_encode(const struct mg_str src);
 
 #ifdef __cplusplus
diff --git a/test/unit_test.c b/test/unit_test.c
index f496b27c5..485403595 100644
--- a/test/unit_test.c
+++ b/test/unit_test.c
@@ -928,10 +928,38 @@ static const char *test_mg_uri_to_local_path(void) {
 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/!");
-  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");
-  free((void *) encoded.p);
+  {
+    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");
+    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%"
+                    "7eoink%28oink%29oink%2f%21");
+    free((void *) encoded.p);
+  }
+  {
+    struct mg_str encoded = mg_url_encode_opt(encode_me, mg_mk_str(" /!"),
+                                              MG_URL_ENCODE_F_UPPERCASE_HEX);
+    ASSERT_MG_STREQ(encoded,
+                    "I%27m "
+                    "a%2Elittle%5Ftea%2Dpot%2Chere%27s%24my%3Bspout%7Eoink%"
+                    "28oink%29oink/!");
+    free((void *) encoded.p);
+  }
+  {
+    struct mg_str encoded = mg_url_encode_opt(
+        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%"
+                    "7Eoink%28oink%29oink/!");
+    free((void *) encoded.p);
+  }
   return NULL;
 }
 
-- 
GitLab