[Tarantool-patches] [PATCH 1/2] httpc: introduce url_escape/url_unescape functions

olegrok at tarantool.org olegrok at tarantool.org
Thu Jan 9 11:04:13 MSK 2020


From: Oleg Babin <babinoleg at mail.ru>

cURL easy API contains functions curl_easy_escape and
curl_easy_unescape:

  - https://curl.haxx.se/libcurl/c/curl_easy_escape.html
  - https://curl.haxx.se/libcurl/c/curl_easy_unescape.html

This functions can be sometimes useful for our users
especially when they works with http client and form
query arguments for http request

This patch introduces two functions available from
our http client:
```lua
httpc.url_escape
httpc.url_unescape
```

Need for #3682
---
Issue: https://github.com/tarantool/tarantool/issues/3682
My related github PR: https://github.com/tarantool/tarantool/pull/4153
 src/httpc.c                       | 19 +++++++++++++
 src/httpc.h                       | 36 +++++++++++++++++++++++++
 src/lua/httpc.c                   | 45 +++++++++++++++++++++++++++++++
 src/lua/httpc.lua                 | 17 +++++++++---
 test/app-tap/http_client.test.lua | 21 ++++++++++++++-
 5 files changed, 134 insertions(+), 4 deletions(-)

diff --git a/src/httpc.c b/src/httpc.c
index be73e3684..d152275e3 100644
--- a/src/httpc.c
+++ b/src/httpc.c
@@ -467,3 +467,22 @@ httpc_execute(struct httpc_request *req, double timeout)
 
 	return 0;
 }
+
+char *
+httpc_str_escape(struct httpc_env *env, const char *str, int len)
+{
+	return curl_easy_escape(env->curl_env.multi, str, len);
+}
+
+char *
+httpc_str_unescape(struct httpc_env *env, const char *str, int len,
+		int *outlen)
+{
+	return curl_easy_unescape(env->curl_env.multi, str, len, outlen);
+}
+
+void
+httpc_str_free(char *str)
+{
+	curl_free(str);
+}
diff --git a/src/httpc.h b/src/httpc.h
index 94d543a40..22a83f72c 100644
--- a/src/httpc.h
+++ b/src/httpc.h
@@ -413,4 +413,40 @@ httpc_execute(struct httpc_request *req, double timeout);
 
 /** Request }}} */
 
+/** {{{ Encode/Decode */
+
+/**
+ * This function URL encodes the given string
+ * according RFC3986
+ * @param req - reference to request object with filled fields
+ * @param str - input string to be encoded
+ * @param len - length of input string
+ * @return encoded string or NULL
+ */
+char *
+httpc_str_escape(struct httpc_env *env, const char *str, int len);
+
+/**
+ * This function URL decodes the given string
+ * according RFC3986
+ * @param req - reference to request object with filled fields
+ * @param str - input string to be encoded
+ * @param len - length of input string
+ * @param outlen - - length of output string
+ * @return decoded string or NULL
+ */
+
+char *
+httpc_str_unescape(struct httpc_env *env, const char *str, int len, int *outlen);
+
+/**
+ * This function reclaims memory that has been obtained
+ * through a libcurl call
+ * @param str - pointer to string returned from escape/unescape
+ */
+void
+httpc_str_free(char *str);
+
+/** Encode/Decode }}} */
+
 #endif /* TARANTOOL_HTTPC_H_INCLUDED */
diff --git a/src/lua/httpc.c b/src/lua/httpc.c
index c3dd611fa..e0c55f49e 100644
--- a/src/lua/httpc.c
+++ b/src/lua/httpc.c
@@ -425,6 +425,49 @@ luaT_httpc_cleanup(lua_State *L)
 	return 2;
 }
 
+static int
+luaT_httpc_escape(lua_State *L)
+{
+	struct httpc_env *ctx = luaT_httpc_checkenv(L);
+	if (ctx == NULL)
+		return luaL_error(L, "can't get httpc environment");
+
+	size_t str_len;
+	const char *str = luaL_checklstring(L, 2, &str_len);
+	if (str == NULL)
+		return luaL_error(L, "second argument should be a string");
+
+	char *outstr = httpc_str_escape(ctx, str, str_len);
+	if (outstr == NULL)
+		return luaL_error(L, "string escaping error");
+
+	lua_pushstring(L, outstr);
+	httpc_str_free(outstr);
+	return 1;
+}
+
+static int
+luaT_httpc_unescape(lua_State *L)
+{
+	struct httpc_env *ctx = luaT_httpc_checkenv(L);
+	if (ctx == NULL)
+		return luaL_error(L, "can't get httpc environment");
+
+	size_t str_len;
+	const char *str = luaL_checklstring(L, 2, &str_len);
+	if (str == NULL)
+		return luaL_error(L, "second argument should be a string");
+
+	int outstr_len;
+	char *outstr = httpc_str_unescape(ctx, str, str_len, &outstr_len);
+	if (outstr == NULL)
+		return luaL_error(L, "string unescaping error");
+
+	lua_pushlstring(L, outstr, outstr_len);
+	httpc_str_free(outstr);
+	return 1;
+}
+
 /*
  * }}}
  */
@@ -435,6 +478,8 @@ luaT_httpc_cleanup(lua_State *L)
 
 static const struct luaL_Reg Module[] = {
 	{"new", luaT_httpc_new},
+	{"escape", luaT_httpc_escape},
+	{"unescape", luaT_httpc_unescape},
 	{NULL, NULL}
 };
 
diff --git a/src/lua/httpc.lua b/src/lua/httpc.lua
index 6381c6a1a..33cb78f3c 100644
--- a/src/lua/httpc.lua
+++ b/src/lua/httpc.lua
@@ -29,8 +29,6 @@
 --  SUCH DAMAGE.
 --
 
-local fiber = require('fiber')
-
 local driver = package.loaded.http.client
 package.loaded.http = nil
 
@@ -442,7 +440,20 @@ curl_mt = {
 -- Export
 --
 local http_default = http_new()
-local this_module = { new = http_new, }
+
+local function url_escape(str)
+    return driver.escape(http_default.curl, str)
+end
+
+local function url_unescape(str)
+    return driver.unescape(http_default.curl, str)
+end
+
+local this_module = {
+    new = http_new,
+    url_escape = url_escape,
+    url_unescape = url_unescape
+}
 
 local function http_default_wrap(fname)
     return function(...) return http_default[fname](http_default, ...) end
diff --git a/test/app-tap/http_client.test.lua b/test/app-tap/http_client.test.lua
index b85b605cf..3676f56b4 100755
--- a/test/app-tap/http_client.test.lua
+++ b/test/app-tap/http_client.test.lua
@@ -611,7 +611,7 @@ function run_tests(test, sock_family, sock_addr)
     stop_server(test, server)
 end
 
-test:plan(2)
+test:plan(3)
 
 test:test("http over AF_INET", function(test)
     local s = socketlib('AF_INET', 'SOCK_STREAM', 0)
@@ -634,4 +634,23 @@ test:test("http over AF_UNIX", function(test)
     os.remove(path)
 end)
 
+test:test("url_escape/url_unescape", function(test)
+    test:plan(11)
+    test:is('hello', client.url_escape('hello'), 'correct encoding of eng')
+    test:is('%D0%BF%D1%80%D0%B8%D0%B2%D0%B5%D1%82', client.url_escape('привет'), 'correct encoding of rus')
+
+    test:is('hello', client.url_unescape('hello'), 'correct decoding of eng')
+    test:is('привет', client.url_unescape('%D0%BF%D1%80%D0%B8%D0%B2%D0%B5%D1%82'), 'correct decoding of rus')
+
+    test:is('hello', client.url_escape(client.url_unescape('hello')), 'decoding and encoding of "hello"')
+    test:is('привет', client.url_unescape(client.url_escape('привет')), 'encoding and decoding of "привет"')
+
+    test:is('%00', client.url_escape(client.url_unescape('%00')), 'decoding and encoding of %00')
+    test:is('\0', client.url_unescape(client.url_escape('\0')), 'encoding and decoding of \\0')
+
+    test:is('%20', client.url_escape(' '), 'space is escaped as %20')
+    test:is('%24%26%2B%2C%3A%3B%3D%3F%40', client.url_escape('$&+,:;=?@'), 'special characters escaping')
+    test:is('-._~', client.url_escape('-._~'), '-._~ are not escaped according RFC 3986')
+end)
+
 os.exit(test:check() == true and 0 or -1)
-- 
2.23.0



More information about the Tarantool-patches mailing list