From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from smtpng1.m.smailru.net (smtpng1.m.smailru.net [94.100.181.251]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by dev.tarantool.org (Postfix) with ESMTPS id 6A3B446970E for ; Thu, 9 Jan 2020 11:04:34 +0300 (MSK) From: olegrok@tarantool.org Date: Thu, 9 Jan 2020 11:04:13 +0300 Message-Id: <360e3a35ff40d3dce9c86f3856f24925bdb8b921.1577881257.git.babinoleg@mail.ru> In-Reply-To: References: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: 8bit Subject: [Tarantool-patches] [PATCH 1/2] httpc: introduce url_escape/url_unescape functions List-Id: Tarantool development patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , To: tarantool-patches@dev.tarantool.org, alexander.turenko@tarantool.org Cc: Oleg Babin From: Oleg Babin 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