Skip to content

Commit

Permalink
httpc: add url_encode and url_decode methods
Browse files Browse the repository at this point in the history
Lua FFI bindings of curl_easy_escape and curl_easy_unescape.

Closes tarantool#3682

@TarantoolBot document
Title: httpc: url_encode and url_decode functions
  * http_client.url_encode - URL encodes the given string
  * http_cleint.url_decode - URL decodes the given string

See:
  https://curl.haxx.se/libcurl/c/curl_easy_escape.html
  https://curl.haxx.se/libcurl/c/curl_easy_unescape.html
  • Loading branch information
olegrok committed Apr 24, 2019
1 parent daf1ced commit db509d0
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 4 deletions.
70 changes: 68 additions & 2 deletions src/lua/httpc.lua
Expand Up @@ -29,7 +29,7 @@
-- SUCH DAMAGE.
--

local fiber = require('fiber')
local ffi = require('ffi')

local driver = package.loaded.http.client
package.loaded.http = nil
Expand Down Expand Up @@ -424,11 +424,77 @@ curl_mt = {
},
}


local err_string_arg = "bad argument #%d to '%s' (%s expected, got %s)"
local url_decode_outlength = ffi.new("int[1]")
ffi.cdef([[
typedef void CURL;
CURL *curl_easy_init(void);
void curl_easy_cleanup(CURL *handle);
char *curl_easy_escape(CURL *handle, const char *string, int length);
char *curl_easy_unescape(CURL *handle, const char *string, int length, int *outlength);
void curl_free(void *p);
]])

--- URL encodes the given string
-- See https://curl.haxx.se/libcurl/c/curl_easy_escape.html
-- @function url_encode
-- @string inp the string
-- @returns result string or nil, err
local function url_encode(inp)
if type(inp) ~= 'string' then
error(err_string_arg:format(1, "http_client.url_encode", 'string', type(inp)), 2)
end
local handle = ffi.C.curl_easy_init()
if not handle then
return nil, 'curl_easy_init error'
end

local escaped_str = ffi.C.curl_easy_escape(handle, inp, #inp)
ffi.C.curl_easy_cleanup(handle)
if escaped_str == nil then
return nil, 'curl_easy_escape error'
end

local out = ffi.string(escaped_str)
ffi.C.curl_free(escaped_str)
return out
end

--- URL decodes the given string
-- See https://curl.haxx.se/libcurl/c/curl_easy_unescape.html
-- @function url_decode
-- @string inp the string
-- @returns result string or nil, err
local function url_decode(inp)
if type(inp) ~= 'string' then
error(err_string_arg:format(1, "http_client.url_decode", 'string', type(inp)), 2)
end
local handle = ffi.C.curl_easy_init()
if not handle then
return nil, 'curl_easy_init error'
end

local unescaped_str = ffi.C.curl_easy_unescape(handle, inp, #inp, url_decode_outlength)
ffi.C.curl_easy_cleanup(handle)
if unescaped_str == nil then
return nil, 'curl_easy_unescape error'
end

local out = ffi.string(unescaped_str, url_decode_outlength[0])
ffi.C.curl_free(unescaped_str)
return out
end

--
-- Export
--
local http_default = http_new()
local this_module = { new = http_new, }
local this_module = {
new = http_new,
url_encode = url_encode,
url_decode = url_decode,
}

local function http_default_wrap(fname)
return function(...) return http_default[fname](http_default, ...) end
Expand Down
1 change: 0 additions & 1 deletion src/lua/string.lua
Expand Up @@ -409,7 +409,6 @@ local function string_rstrip(inp, chars)
return ffi.string(casted_inp + strip_newstart[0], strip_newlen[0])
end


-- It'll automatically set string methods, too.
local string = require('string')
string.split = string_split
Expand Down
21 changes: 20 additions & 1 deletion test/app-tap/http_client.test.lua
Expand Up @@ -583,7 +583,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)
Expand All @@ -606,4 +606,23 @@ test:test("http over AF_UNIX", function(test)
os.remove(path)
end)

test:test("url_encode/url_decode", function(test)
test:plan(11)
test:is('hello', client.url_encode('hello'), 'correct encoding of eng')
test:is('%D0%BF%D1%80%D0%B8%D0%B2%D0%B5%D1%82', client.url_encode('привет'), 'correct encoding of rus')

test:is('hello', client.url_decode('hello'), 'correct decoding of eng')
test:is('привет', client.url_decode('%D0%BF%D1%80%D0%B8%D0%B2%D0%B5%D1%82'), 'correct decoding of rus')

test:is('hello', client.url_encode(client.url_decode('hello')), 'decoding and encoding of "hello"')
test:is('привет', client.url_decode(client.url_encode('привет')), 'encoding and decoding of "привет"')

test:is('%00', client.url_encode(client.url_decode('%00')), 'decoding and encoding of %00')
test:is('\0', client.url_decode(client.url_encode('\0')), 'encoding and decoding of \\0')

test:is('%20', client.url_encode(' '), 'space is escaped as %20')
test:is('%24%26%2B%2C%3A%3B%3D%3F%40', client.url_encode('$&+,:;=?@'), 'special characters escaping')
test:is('-._~', client.url_encode('-._~'), '-._~ are not escaped according RFC 3986')
end)

os.exit(test:check() == true and 0 or -1)

0 comments on commit db509d0

Please sign in to comment.