luci-base: add urldecode() and urlencode() C implementations
authorJo-Philipp Wich <jo@mein.io>
Mon, 9 Apr 2018 07:47:40 +0000 (09:47 +0200)
committerJo-Philipp Wich <jo@mein.io>
Tue, 10 Apr 2018 09:41:32 +0000 (11:41 +0200)
The C implementations of urlencode and urldecode are considerably faster
than their current Lua counterparts.

On an AMD Geode system, the C variant is up to ten times faster when
decoding strings and up to four times faster when encoding them.

The functions are also designed to only allocate new strings when any
actual changes are required, otherwise they reuse the existing input
strings, reducing the overal memory usage somewhat.

Signed-off-by: Jo-Philipp Wich <jo@mein.io>
modules/luci-base/src/template_lualib.c
modules/luci-base/src/template_utils.c
modules/luci-base/src/template_utils.h

index d5c8dd6b4c9f1f20c3da8eeebd45bf13a20ea6ae..45e23966e973c8b88bb63167a891ed0f16ee0751 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * LuCI Template - Lua binding
  *
- *   Copyright (C) 2009 Jo-Philipp Wich <jow@openwrt.org>
+ *   Copyright (C) 2009-2018 Jo-Philipp Wich <jo@mein.io>
  *
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
@@ -110,6 +110,51 @@ int template_L_striptags(lua_State *L)
        return 0;
 }
 
+int template_L_urlencode(lua_State *L)
+{
+       size_t len = 0;
+       const char *str = luaL_checkstring(L, 1);
+       char *res = urlencode(str, &len);
+
+       if (res != NULL)
+       {
+               lua_pushlstring(L, res, len);
+               free(res);
+
+               return 1;
+       }
+       else if (len == 0)
+       {
+               lua_pushvalue(L, 1);
+               return 1;
+       }
+
+       return 0;
+}
+
+int template_L_urldecode(lua_State *L)
+{
+       size_t len = 0;
+       const char *str = luaL_checkstring(L, 1);
+       int keep_plus = lua_toboolean(L, 2);
+       char *res = urldecode(str, &len, keep_plus == 1);
+
+       if (res != NULL)
+       {
+               lua_pushlstring(L, res, len);
+               free(res);
+
+               return 1;
+       }
+       else if (len == 0)
+       {
+               lua_pushvalue(L, 1);
+               return 1;
+       }
+
+       return 0;
+}
+
 static int template_L_load_catalog(lua_State *L) {
        const char *lang = luaL_optstring(L, 1, "en");
        const char *dir  = luaL_optstring(L, 2, NULL);
@@ -165,6 +210,8 @@ static const luaL_reg R[] = {
        { "utf8",                               template_L_utf8 },
        { "pcdata",                             template_L_pcdata },
        { "striptags",                  template_L_striptags },
+       { "urlencode",                  template_L_urlencode },
+       { "urldecode",                  template_L_urldecode },
        { "load_catalog",               template_L_load_catalog },
        { "close_catalog",              template_L_close_catalog },
        { "change_catalog",             template_L_change_catalog },
index 3979487f1276453ca32d06d26e8eb4de0584114b..eefdd17008cf3e7c1506873e2036700aa2abccf1 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * LuCI Template - Utility functions
  *
- *   Copyright (C) 2010 Jo-Philipp Wich <jow@openwrt.org>
+ *   Copyright (C) 2010-2018 Jo-Philipp Wich <jo@mein.io>
  *
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
@@ -428,6 +428,128 @@ char * striptags(const char *s, unsigned int l)
        return buf_destroy(buf);
 }
 
+
+static inline bool is_urlencode_char(char c)
+{
+       return !((c >= 'a' && c <= 'z') ||
+                (c >= 'A' && c <= 'Z') ||
+                (c >= '0' && c <= '9') ||
+                (c == '$') || (c == '_') ||
+                (c == '-') || (c == '.') ||
+                (c == '~'));
+}
+
+/*
+ * URL-encode all special characters in given string and return
+ * encoded copy.
+ *
+ * If no encoding was required, returns NULL. If an encoded_len
+ * pointer is passed, it is set to the length of the encoded string.
+ *
+ * Sets encoded_len and returns NULL if memory allocation failed.
+ */
+char *urlencode(const char *s, size_t *encoded_len)
+{
+       size_t i, enc_len;
+       char *enc, *ptr;
+
+       for (i = 0, enc_len = 0; s[i]; i++)
+               if (is_urlencode_char(s[i]))
+                       enc_len += 3;
+               else
+                       enc_len++;
+
+       if (i != enc_len)
+       {
+               if (encoded_len)
+                       *encoded_len = enc_len;
+
+               enc = calloc(1, enc_len + 1);
+
+               if (!enc)
+                       return NULL;
+
+               for (i = 0, ptr = enc; s[i]; i++)
+                       if (is_urlencode_char(s[i]))
+                               ptr += snprintf(ptr, 4, "%%%02x", (unsigned char)s[i]);
+                       else
+                               *ptr++ = s[i];
+
+               return enc;
+       }
+
+       return NULL;
+}
+
+/*
+ * URL-decode given string and return decoded copy.
+ *
+ * If no decoding was required, returns NULL. If an decoded_len
+ * pointer is passed, it is set to the length of the decoded string.
+ *
+ * When keep_plus is true, skip decoding of plus ("+") signs into
+ * space (0x20) characters.
+ *
+ * Sets decoded_len and returns NULL if memory allocation failed.
+ */
+
+#define hex(x) \
+       (((x) <= '9') ? ((x) - '0') : \
+               (((x) <= 'F') ? ((x) - 'A' + 10) : \
+                       ((x) - 'a' + 10)))
+
+char *urldecode(const char *s, size_t *decoded_len, bool keep_plus)
+{
+       bool changed = false;
+       size_t i, dec_len;
+       char *dec, *ptr;
+
+       for (i = 0, dec_len = 0; s[i]; i++, dec_len++)
+       {
+               if (s[i] == '%' && isxdigit(s[i+1]) && isxdigit(s[i+2]))
+               {
+                       changed = true;
+                       i += 2;
+               }
+               else if (!keep_plus && s[i] == '+')
+               {
+                       changed = true;
+               }
+       }
+
+       if (changed)
+       {
+               if (decoded_len)
+                       *decoded_len = dec_len;
+
+               dec = calloc(1, dec_len + 1);
+
+               if (!dec)
+                       return NULL;
+
+               for (i = 0, ptr = dec; s[i]; i++)
+               {
+                       if (s[i] == '%' && isxdigit(s[i+1]) && isxdigit(s[i+2]))
+                       {
+                               *ptr++ = (char)(16 * hex(s[i+1]) + hex(s[i+2]));
+                               i += 2;
+                       }
+                       else if (!keep_plus && s[i] == '+')
+                       {
+                               *ptr++ = ' ';
+                       }
+                       else
+                       {
+                               *ptr++ = s[i];
+                       }
+               }
+
+               return dec;
+       }
+
+       return NULL;
+}
+
 void luastr_escape(struct template_buffer *out, const char *s, unsigned int l,
                                   int escape_xml)
 {
index 32a79f93bcd33ad30c6e626244b46dd263b68266..872ecb072f5ad2bd71bcb498b93c01a1e823e79f 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * LuCI Template - Utility header
  *
- *   Copyright (C) 2010-2012 Jo-Philipp Wich <jow@openwrt.org>
+ *   Copyright (C) 2010-2018 Jo-Philipp Wich <jo@mein.io>
  *
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
@@ -22,6 +22,8 @@
 #include <stdlib.h>
 #include <stdio.h>
 #include <string.h>
+#include <stdbool.h>
+#include <ctype.h>
 
 
 /* buffer object */
@@ -42,6 +44,8 @@ char * buf_destroy(struct template_buffer *buf);
 char * utf8(const char *s, unsigned int l);
 char * pcdata(const char *s, unsigned int l);
 char * striptags(const char *s, unsigned int l);
+char * urlencode(const char *s, size_t *encoded_len);
+char * urldecode(const char *s, size_t *decoded_len, bool keep_plus);
 
 void luastr_escape(struct template_buffer *out, const char *s, unsigned int l, int escape_xml);
 void luastr_translate(struct template_buffer *out, const char *s, unsigned int l, int escape_xml);