[tarantool-patches] Re: [PATCH] httpc: add checking of headers in httpc:request
Roman
roman.habibov at tarantool.org
Wed Dec 26 18:56:21 MSK 2018
On 23.12.2018 6:19, Alexander Turenko wrote:
> Hi!
>
> See comments below and proposed fixup on the branch
> romanhabibov/gh-3679-httpc-request-fixups. Please, amend the patch and
> proceed with the next reviewer (Vlad, added to CC).
>
> WBR, Alexander Turenko.
Done. Vlad, review, please.
commit 253be70b02a700ef9936fbab98f80c79de3cf0fe
Author: Roman Khabibov <roman.habibov at tarantool.org>
Date: Wed Dec 26 17:49:34 2018 +0300
httpc: add checking of headers in httpc:request
Add functions that preprocess the request headers. Each header must
be nil, 'string' or 'table'
with '__tostring' metamethod.
Closes #3679
diff --git a/src/lua/httpc.lua b/src/lua/httpc.lua
index cd44b6054..21a7cd2fd 100644
--- a/src/lua/httpc.lua
+++ b/src/lua/httpc.lua
@@ -216,6 +216,42 @@ local function process_cookies(cookies)
return result
end
+local function bad_header_error(func_name, header_name)
+ error(('%s: cannot convert header "%s" to a string'):format(
+ func_name, header_name))
+end
+
+-- Verify and preprocess outcoming headers before send a request.
+--
+-- Return the new headers table (with all string values) or nil
+-- when no headers were provided.
+local function preprocess_request_headers(headers)
+ local func_name = 'preprocess_request_headers'
+
+ if headers == nil then
+ return nil
+ end
+
+ local res = {}
+
+ for name, value in pairs(headers) do
+ local lua_type = type(value)
+ if lua_type == 'string' then
+ res[name] = value
+ elseif lua_type == 'table' then
+ local mt = getmetatable(value)
+ if mt == nil or mt.__tostring == nil then
+ bad_header_error(func_name, name)
+ end
+ res[name] = tostring(value)
+ else
+ bad_header_error(func_name, name)
+ end
+ end
+
+ return res
+end
+
local function process_headers(headers)
for header, value in pairs(headers) do
if type(value) == 'table' then
@@ -296,6 +332,12 @@ curl_mt = {
if not method or not url then
error('request(method, url [, options]])')
end
+ local headers = preprocess_request_headers(opts and
opts.headers)
+ if headers ~= nil then
+ -- prevent changing of user provided options
+ opts = table.copy(opts)
+ opts.headers = headers
+ end
local resp = self.curl:request(method, url, body, opts or {})
if resp and resp.headers then
if resp.headers['set-cookie'] ~= nil then
diff --git a/test/app-tap/http_client.test.lua
b/test/app-tap/http_client.test.lua
index 10a3728f8..4ae382fe7 100755
--- a/test/app-tap/http_client.test.lua
+++ b/test/app-tap/http_client.test.lua
@@ -205,6 +205,81 @@ local function test_errors(test)
test:is(r.status, 595, "GET: response on bad url")
end
+-- gh-3679 allow only headers can be converted to string
+local function test_request_headers(test, url, opts)
+ local exp_err = 'preprocess_request_headers: cannot convert header ' ..
+ '"aaa" to a string'
+ local cases = {
+ {
+ 'string header',
+ opts = {headers = {aaa = 'aaa'}},
+ exp_err = nil,
+ },
+ {
+ 'header with __tostring() metamethod',
+ opts = {headers = {aaa = setmetatable({}, {
+ __tostring = function(self)
+ return 'aaa'
+ end})}},
+ exp_err = nil,
+ postrequest_check = function(opts)
+ assert(type(opts.headers.aaa) == 'table',
+ '"aaa" header was modified in http_client')
+ end,
+ },
+ {
+ 'boolean header',
+ opts = {headers = {aaa = true}},
+ exp_err = exp_err,
+ },
+ {
+ 'number header',
+ opts = {headers = {aaa = 10}},
+ exp_err = exp_err,
+ },
+ {
+ 'cdata header (box.NULL)',
+ opts = {headers = {aaa = box.NULL}},
+ exp_err = exp_err,
+ },
+ {
+ 'cdata<uint64_t> header',
+ opts = {headers = {aaa = 10ULL}},
+ exp_err = exp_err,
+ },
+ {
+ 'table header w/o metatable',
+ opts = {headers = {aaa = {}}},
+ exp_err = exp_err,
+ },
+ {
+ 'table header w/o __tostring() metamethod',
+ opts = {headers = {aaa = setmetatable({}, {})}},
+ exp_err = exp_err,
+ },
+ }
+ test:plan(#cases)
+
+ local http = client:new()
+
+ for _, case in ipairs(cases) do
+ local opts = merge(table.copy(opts), case.opts)
+ local ok, err = pcall(http.get, http, url, opts)
+ if case.postrequest_check ~= nil then
+ case.postrequest_check(opts)
+ end
+ if case.exp_err == nil then
+ -- expect success
+ test:ok(ok, case[1])
+ else
+ -- expect fail
+ assert(type(err) == 'string')
+ err = err:gsub('^builtin/[a-z._]+.lua:[0-9]+: ', '')
+ test:is_deeply({ok, err}, {false, case.exp_err}, case[1])
+ end
+ end
+end
+
local function test_headers(test, url, opts)
test:plan(19)
local http = client:new()
@@ -397,12 +472,13 @@ local function test_concurrent(test, url, opts)
end
function run_tests(test, sock_family, sock_addr)
- test:plan(9)
+ test:plan(10)
local server, url, opts = start_server(test, sock_family, sock_addr)
test:test("http.client", test_http_client, url, opts)
test:test("cancel and errinj", test_cancel_and_errinj, url ..
'long_query', opts)
test:test("basic http post/get", test_post_and_get, url, opts)
test:test("errors", test_errors)
+ test:test("request_headers", test_request_headers, url, opts)
test:test("headers", test_headers, url, opts)
test:test("special methods", test_special_methods, url, opts)
if sock_family == 'AF_UNIX' and jit.os ~= "Linux" then
More information about the Tarantool-patches
mailing list