Tarantool development patches archive
 help / color / mirror / Atom feed
* [tarantool-patches] [PATCH] httpc: add checking of headers in httpc:request
@ 2018-12-21 18:18 Roman Khabibov
  2018-12-23  3:19 ` [tarantool-patches] " Alexander Turenko
  2019-02-25 14:51 ` Kirill Yukhin
  0 siblings, 2 replies; 18+ messages in thread
From: Roman Khabibov @ 2018-12-21 18:18 UTC (permalink / raw)
  To: tarantool-patches; +Cc: alexander.turenko

Add function that checks the value of each header. It must be 'string' or 'table'
with '__tostring' metamethod.

Closes #3679
---

Branch: https://github.com/tarantool/tarantool/tree/romanhabibov/gh-3679-httpc-request
Issue: https://github.com/tarantool/tarantool/issues/3679

 src/lua/httpc.lua                 | 23 +++++++++++++++++++++++
 test/app-tap/http_client.test.lua | 17 +++++++++++++++++
 2 files changed, 40 insertions(+)

diff --git a/src/lua/httpc.lua b/src/lua/httpc.lua
index cd44b6054..ede976a3a 100644
--- a/src/lua/httpc.lua
+++ b/src/lua/httpc.lua
@@ -216,6 +216,28 @@ local function process_cookies(cookies)
     return result
 end
 
+local function check_headers(opts)
+    if opts ~= nil then
+        local headers = opts["headers"]
+        if headers ~= nil then
+            for header, value in pairs(headers) do
+                if type(value) ~= 'string' then
+                    if  type(value) ~= 'table' then
+                        error('Usage: httpc:request bad \''..header..'\' value')
+                    end
+                    local mt = getmetatable(value)
+                    if mt == nil then
+                        error('Usage: httpc:request \''..header..'\' has not \'__tostring\'')
+                    end
+                    if mt["__tostring"] ~= nil then
+                        headers[header] = tostring(value)
+                    end
+                end
+            end
+        end
+    end
+end
+
 local function process_headers(headers)
     for header, value in pairs(headers) do
         if type(value) == 'table' then
@@ -296,6 +318,7 @@ curl_mt = {
             if not method or not url then
                 error('request(method, url [, options]])')
             end
+            check_headers(opts)
             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..d7e01d60f 100755
--- a/test/app-tap/http_client.test.lua
+++ b/test/app-tap/http_client.test.lua
@@ -205,6 +205,23 @@ local function test_errors(test)
     test:is(r.status, 595, "GET: response on bad url")
 end
 
+--gh-3679 Check that client check that the httpc:request doesn't modify headers.
+
+local function test_request_headers(test)
+    test:plan(5)
+    httpc = require('http.client').new()
+    test:ok(not pcall(httpc:request('GET', 'localhost:4444', nil, {headers = {aaa = true}})),
+            'expected error about bad value')
+    test:ok(not pcall(httpc:request('GET', 'localhost:4444', nil, {headers = {aaa = 10}})),
+            'expected error about bad value')
+    test:ok(not pcall(httpc:request('GET', 'localhost:4444', nil, {headers = {aaa = box.NULL}})),
+            'expected error about bad value')
+    test:ok(not pcall(httpc:request('GET', 'localhost:4444', nil, {headers = {aaa = 10ULL}})),
+            'expected error about bad value')
+    test:ok(not pcall(httpc:request('GET', 'localhost:4444', nil, {headers = {aaa = {}}})),
+            'expected error with no \'__tostring\'')
+end
+
 local function test_headers(test, url, opts)
     test:plan(19)
     local http = client:new()
-- 
2.17.1

^ permalink raw reply	[flat|nested] 18+ messages in thread

* [tarantool-patches] Re: [PATCH] httpc: add checking of headers in httpc:request
  2018-12-21 18:18 [tarantool-patches] [PATCH] httpc: add checking of headers in httpc:request Roman Khabibov
@ 2018-12-23  3:19 ` Alexander Turenko
  2018-12-26 15:56   ` Roman
  2019-02-25 14:51 ` Kirill Yukhin
  1 sibling, 1 reply; 18+ messages in thread
From: Alexander Turenko @ 2018-12-23  3:19 UTC (permalink / raw)
  To: Roman Khabibov; +Cc: tarantool-patches, Vladislav Shpilevoy

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.

On Fri, Dec 21, 2018 at 09:18:44PM +0300, Roman Khabibov wrote:
> Add function that checks the value of each header. It must be 'string' or 'table'
> with '__tostring' metamethod.
> 
> Closes #3679
> ---
> 
> Branch: https://github.com/tarantool/tarantool/tree/romanhabibov/gh-3679-httpc-request
> Issue: https://github.com/tarantool/tarantool/issues/3679
> 
>  src/lua/httpc.lua                 | 23 +++++++++++++++++++++++
>  test/app-tap/http_client.test.lua | 17 +++++++++++++++++
>  2 files changed, 40 insertions(+)
> 
> diff --git a/src/lua/httpc.lua b/src/lua/httpc.lua
> index cd44b6054..ede976a3a 100644
> --- a/src/lua/httpc.lua
> +++ b/src/lua/httpc.lua
> @@ -216,6 +216,28 @@ local function process_cookies(cookies)
>      return result
>  end
>  
> +local function check_headers(opts)
> +    if opts ~= nil then
> +        local headers = opts["headers"]
> +        if headers ~= nil then
> +            for header, value in pairs(headers) do
> +                if type(value) ~= 'string' then
> +                    if  type(value) ~= 'table' then
> +                        error('Usage: httpc:request bad \''..header..'\' value')
> +                    end
> +                    local mt = getmetatable(value)
> +                    if mt == nil then
> +                        error('Usage: httpc:request \''..header..'\' has not \'__tostring\'')
> +                    end
> +                    if mt["__tostring"] ~= nil then
> +                        headers[header] = tostring(value)
> +                    end
> +                end
> +            end
> +        end
> +    end
> +end
> +

1. Too common name, IMHO.
2. Can be implemented with much less indentation level.
3. Changes user provided options.

>  local function process_headers(headers)
>      for header, value in pairs(headers) do
>          if type(value) == 'table' then
> @@ -296,6 +318,7 @@ curl_mt = {
>              if not method or not url then
>                  error('request(method, url [, options]])')
>              end
> +            check_headers(opts)
>              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..d7e01d60f 100755
> --- a/test/app-tap/http_client.test.lua
> +++ b/test/app-tap/http_client.test.lua
> @@ -205,6 +205,23 @@ local function test_errors(test)
>      test:is(r.status, 595, "GET: response on bad url")
>  end
>  
> +--gh-3679 Check that client check that the httpc:request doesn't modify headers.
> +
> +local function test_request_headers(test)
> +    test:plan(5)
> +    httpc = require('http.client').new()
> +    test:ok(not pcall(httpc:request('GET', 'localhost:4444', nil, {headers = {aaa = true}})),
> +            'expected error about bad value')
> +    test:ok(not pcall(httpc:request('GET', 'localhost:4444', nil, {headers = {aaa = 10}})),
> +            'expected error about bad value')
> +    test:ok(not pcall(httpc:request('GET', 'localhost:4444', nil, {headers = {aaa = box.NULL}})),
> +            'expected error about bad value')
> +    test:ok(not pcall(httpc:request('GET', 'localhost:4444', nil, {headers = {aaa = 10ULL}})),
> +            'expected error about bad value')
> +    test:ok(not pcall(httpc:request('GET', 'localhost:4444', nil, {headers = {aaa = {}}})),
> +            'expected error with no \'__tostring\'')
> +end
> +

1. Don't called.
2. Don't check error messages.
3. Too long lines.
4. Will not work in strict mode (require('strict').on() or Debug build).
5. pcall is called for the result, so it will not work as you expected;
   see below.
6. The message above the function is not about what the function does
   and is not what the issue states.

Re 5, compare:

pcall(error, 'hello')
pcall(error('hello'))

It is sad that you not even check whether your test was run.

>  local function test_headers(test, url, opts)
>      test:plan(19)
>      local http = client:new()
> -- 
> 2.17.1
> 

^ permalink raw reply	[flat|nested] 18+ messages in thread

* [tarantool-patches] Re: [PATCH] httpc: add checking of headers in httpc:request
  2018-12-23  3:19 ` [tarantool-patches] " Alexander Turenko
@ 2018-12-26 15:56   ` Roman
  2018-12-29 10:30     ` Vladislav Shpilevoy
  0 siblings, 1 reply; 18+ messages in thread
From: Roman @ 2018-12-26 15:56 UTC (permalink / raw)
  To: tarantool-patches, Alexander Turenko; +Cc: Vladislav Shpilevoy


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@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

^ permalink raw reply	[flat|nested] 18+ messages in thread

* [tarantool-patches] Re: [PATCH] httpc: add checking of headers in httpc:request
  2018-12-26 15:56   ` Roman
@ 2018-12-29 10:30     ` Vladislav Shpilevoy
  2019-01-09 13:29       ` Roman
  0 siblings, 1 reply; 18+ messages in thread
From: Vladislav Shpilevoy @ 2018-12-29 10:30 UTC (permalink / raw)
  To: Roman, tarantool-patches, Alexander Turenko

Hi! Thanks for the patch! See 5 comments below.

On 26/12/2018 18:56, Roman wrote:
> 
> 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@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

1. Firstly, I think, that the patch should be in C in
lua/httpc.c and affect only function luaT_httpc_request. It
will prevent creation of additional table 'res', maybe
will look even shorter, and evidently will be more efficient.
It is not so complex logic so as to write it in Lua, IMO.

> 
> 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))

2. Broken indentation. We never wrap a line right after '(',
and when we wrap parameters, they should be aligned by '(' of
the first line.

> +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.

3. Please, use @retval when describing return value.

> +local function preprocess_request_headers(headers)
> +    local func_name = 'preprocess_request_headers'

4. For a user this function name is useless. He called
:request(), but in logs doesn't see an appropriate message.

> +
> +    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

5. Already second table copying. This is why I do not like it in Lua.

>               local resp = self.curl:request(method, url, body, opts or {})
>               if resp and resp.headers then
>                   if resp.headers['set-cookie'] ~= nil then

^ permalink raw reply	[flat|nested] 18+ messages in thread

* [tarantool-patches] Re: [PATCH] httpc: add checking of headers in httpc:request
  2018-12-29 10:30     ` Vladislav Shpilevoy
@ 2019-01-09 13:29       ` Roman
  2019-01-24 14:54         ` Roman
  2019-01-29 19:44         ` Vladislav Shpilevoy
  0 siblings, 2 replies; 18+ messages in thread
From: Roman @ 2019-01-09 13:29 UTC (permalink / raw)
  To: tarantool-patches, Alexander Turenko; +Cc: Vladislav Shpilevoy


> 1. Firstly, I think, that the patch should be in C in
> lua/httpc.c and affect only function luaT_httpc_request. It
> will prevent creation of additional table 'res', maybe
> will look even shorter, and evidently will be more efficient.
> It is not so complex logic so as to write it in Lua, IMO.
>
Done.

commit beb0562425dd5062af5d18ae202f0f4434cb5108
Author: Roman Khabibov <roman.habibov@tarantool.org>
Date:   Wed Dec 26 17:49:34 2018 +0300

     httpc: add checking of headers in httpc:request

     Add preprocessing of the request headers. Each header must be nil, 
'string' or 'table'
     with '__tostring' metamethod.

     Closes #3679

diff --git a/src/lua/httpc.c b/src/lua/httpc.c
index 5f4e2e912..16a435ef1 100644
--- a/src/lua/httpc.c
+++ b/src/lua/httpc.c
@@ -173,6 +173,15 @@ luaT_httpc_request(lua_State *L)
      if (!lua_isnil(L, -1)) {
          lua_pushnil(L);
          while (lua_next(L, -2) != 0) {
+            int header_type = lua_type(L, -1);
+            if (header_type != LUA_TSTRING) {
+                if (header_type != LUA_TTABLE) {
+                    return luaL_error(L, "cannot convert header to a 
string");
+                } else if (luaL_getmetafield(L, -1, "__tostring") == 
LUA_TNIL) {
+                    return luaL_error(L, "cannot convert header to a 
string");
+                }
+                lua_pop(L, 1);
+            }
              if (httpc_set_header(req, "%s: %s",
                           lua_tostring(L, -2),
                           lua_tostring(L, -1)) < 0) {
diff --git a/test/app-tap/http_client.test.lua 
b/test/app-tap/http_client.test.lua
index 10a3728f8..d1fde5009 100755
--- a/test/app-tap/http_client.test.lua
+++ b/test/app-tap/http_client.test.lua
@@ -205,6 +205,80 @@ 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 = 'cannot convert header 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 +471,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

^ permalink raw reply	[flat|nested] 18+ messages in thread

* [tarantool-patches] Re: [PATCH] httpc: add checking of headers in httpc:request
  2019-01-09 13:29       ` Roman
@ 2019-01-24 14:54         ` Roman
  2019-01-29 19:44         ` Vladislav Shpilevoy
  1 sibling, 0 replies; 18+ messages in thread
From: Roman @ 2019-01-24 14:54 UTC (permalink / raw)
  To: tarantool-patches, Alexander Turenko; +Cc: Vladislav Shpilevoy


On 09.01.2019 16:29, Roman wrote:
>
>> 1. Firstly, I think, that the patch should be in C in
>> lua/httpc.c and affect only function luaT_httpc_request. It
>> will prevent creation of additional table 'res', maybe
>> will look even shorter, and evidently will be more efficient.
>> It is not so complex logic so as to write it in Lua, IMO.
>>
> Done.
>
> commit beb0562425dd5062af5d18ae202f0f4434cb5108
> Author: Roman Khabibov <roman.habibov@tarantool.org>
> Date:   Wed Dec 26 17:49:34 2018 +0300
>
>     httpc: add checking of headers in httpc:request
>
>     Add preprocessing of the request headers. Each header must be nil, 
> 'string' or 'table'
>     with '__tostring' metamethod.
>
>     Closes #3679
>
> diff --git a/src/lua/httpc.c b/src/lua/httpc.c
> index 5f4e2e912..16a435ef1 100644
> --- a/src/lua/httpc.c
> +++ b/src/lua/httpc.c
> @@ -173,6 +173,15 @@ luaT_httpc_request(lua_State *L)
>      if (!lua_isnil(L, -1)) {
>          lua_pushnil(L);
>          while (lua_next(L, -2) != 0) {
> +            int header_type = lua_type(L, -1);
> +            if (header_type != LUA_TSTRING) {
> +                if (header_type != LUA_TTABLE) {
> +                    return luaL_error(L, "cannot convert header to a 
> string");
> +                } else if (luaL_getmetafield(L, -1, "__tostring") == 
> LUA_TNIL) {
> +                    return luaL_error(L, "cannot convert header to a 
> string");
> +                }
> +                lua_pop(L, 1);
> +            }
>              if (httpc_set_header(req, "%s: %s",
>                           lua_tostring(L, -2),
>                           lua_tostring(L, -1)) < 0) {
> diff --git a/test/app-tap/http_client.test.lua 
> b/test/app-tap/http_client.test.lua
> index 10a3728f8..d1fde5009 100755
> --- a/test/app-tap/http_client.test.lua
> +++ b/test/app-tap/http_client.test.lua
> @@ -205,6 +205,80 @@ 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 = 'cannot convert header 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 +471,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
>
>

^ permalink raw reply	[flat|nested] 18+ messages in thread

* [tarantool-patches] Re: [PATCH] httpc: add checking of headers in httpc:request
  2019-01-09 13:29       ` Roman
  2019-01-24 14:54         ` Roman
@ 2019-01-29 19:44         ` Vladislav Shpilevoy
  2019-01-29 20:32           ` Alexander Turenko
  2019-01-31 23:47           ` Roman
  1 sibling, 2 replies; 18+ messages in thread
From: Vladislav Shpilevoy @ 2019-01-29 19:44 UTC (permalink / raw)
  To: tarantool-patches, Roman, Alexander Turenko

Hi! Thanks for the patch! See 7 comments below.

On 09/01/2019 16:29, Roman wrote:
> 
>> 1. Firstly, I think, that the patch should be in C in
>> lua/httpc.c and affect only function luaT_httpc_request. It
>> will prevent creation of additional table 'res', maybe
>> will look even shorter, and evidently will be more efficient.
>> It is not so complex logic so as to write it in Lua, IMO.
>>
> Done.
> 
> commit beb0562425dd5062af5d18ae202f0f4434cb5108
> Author: Roman Khabibov <roman.habibov@tarantool.org>
> Date:   Wed Dec 26 17:49:34 2018 +0300
> 
>      httpc: add checking of headers in httpc:request
> 
>      Add preprocessing of the request headers. Each header must be nil, 'string' or 'table'
>      with '__tostring' metamethod.
> 
>      Closes #3679
> 
> diff --git a/src/lua/httpc.c b/src/lua/httpc.c
> index 5f4e2e912..16a435ef1 100644
> --- a/src/lua/httpc.c
> +++ b/src/lua/httpc.c
> @@ -173,6 +173,15 @@ luaT_httpc_request(lua_State *L)
>       if (!lua_isnil(L, -1)) {
>           lua_pushnil(L);

1. I see, that you didn't use format-patch + send-email, since here we
have white-spaces, but in the real diff there are tabs. Please, use
format-patch + send-email. Or if you used them, then fix you text editor.
Or stop using web client - use a specialized one like Thunderbird.

>           while (lua_next(L, -2) != 0) {
> +            int header_type = lua_type(L, -1);
> +            if (header_type != LUA_TSTRING) {

2. Why so complex instead of just lua_isstring()?

> +                if (header_type != LUA_TTABLE) {
> +                    return luaL_error(L, "cannot convert header to a string");

3. Word 'cannot' does not exist. Please, use "can't" or "can not".

4. In the commit message you said that 'nil' is ok, but here you did not
check for that and return an error. Of course, real nil can not appear in
a table, because it means absence of a value, but what about box.NULL?

5. Out of 80 symbols.

> +                } else if (luaL_getmetafield(L, -1, "__tostring") == LUA_TNIL) {

6. Why do you compare with LUA_TNIL instead of direct checking of 0/1?
I see, that luaL_getmetafield does return exactly 0 or 1, not LUA_TNIL. Maybe
they accidentally matched, but it is not a reason to use LUA_TNIL here.

> +                    return luaL_error(L, "cannot convert header to a string");
> +                }
> +                lua_pop(L, 1);

7. Why do you pop? Now on a stack we have key (L[-2]) and value (L[-1]).
Here you pops one, and we have key (L[-1]). So the function below does a
mess. And maybe this is a reason why the test below fails.

> +            }
>               if (httpc_set_header(req, "%s: %s",
>                            lua_tostring(L, -2),
>                            lua_tostring(L, -1)) < 0) {
> diff --git a/test/app-tap/http_client.test.lua b/test/app-tap/http_client.test.lua
> index 10a3728f8..d1fde5009 100755
> --- a/test/app-tap/http_client.test.lua
> +++ b/test/app-tap/http_client.test.lua

^ permalink raw reply	[flat|nested] 18+ messages in thread

* [tarantool-patches] Re: [PATCH] httpc: add checking of headers in httpc:request
  2019-01-29 19:44         ` Vladislav Shpilevoy
@ 2019-01-29 20:32           ` Alexander Turenko
  2019-01-31 23:47           ` Roman
  1 sibling, 0 replies; 18+ messages in thread
From: Alexander Turenko @ 2019-01-29 20:32 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches, Roman

> >           while (lua_next(L, -2) != 0) {
> > +            int header_type = lua_type(L, -1);
> > +            if (header_type != LUA_TSTRING) {
> 
> 2. Why so complex instead of just lua_isstring()?

It is not quite same. lua_isstring() accepts numbers as well as strings.

^ permalink raw reply	[flat|nested] 18+ messages in thread

* [tarantool-patches] Re: [PATCH] httpc: add checking of headers in httpc:request
  2019-01-29 19:44         ` Vladislav Shpilevoy
  2019-01-29 20:32           ` Alexander Turenko
@ 2019-01-31 23:47           ` Roman
  2019-02-05 11:42             ` Vladislav Shpilevoy
  1 sibling, 1 reply; 18+ messages in thread
From: Roman @ 2019-01-31 23:47 UTC (permalink / raw)
  To: tarantool-patches, Alexander Turenko; +Cc: Vladislav Shpilevoy

Hello! Thanks for the review.

On 29.01.2019 22:44, Vladislav Shpilevoy wrote:
> Hi! Thanks for the patch! See 7 comments below.
>
> 2. Why so complex instead of just lua_isstring()?

As written by Alexander, it really takes a number.


> 3. Word 'cannot' does not exist. Please, use "can't" or "can not".

Fixed.


> 4. In the commit message you said that 'nil' is ok, but here you did not
> check for that and return an error. Of course, real nil can not appear in
> a table, because it means absence of a value, but what about box.NULL?

Added test for this case, right?


> 5. Out of 80 symbols.
>
>> +                } else if (luaL_getmetafield(L, -1, "__tostring") == 
>> LUA_TNIL) {
>
> 6. Why do you compare with LUA_TNIL instead of direct checking of 0/1?
> I see, that luaL_getmetafield does return exactly 0 or 1, not 
> LUA_TNIL. Maybe
> they accidentally matched, but it is not a reason to use LUA_TNIL here.

Fixed.


>
>> +                    return luaL_error(L, "cannot convert header to a 
>> string");
>> +                }
>> +                lua_pop(L, 1);
>
> 7. Why do you pop? Now on a stack we have key (L[-2]) and value (L[-1]).
> Here you pops one, and we have key (L[-1]). So the function below does a
> mess. And maybe this is a reason why the test below fails.

Because luaL_getmetafield puts on the stack a field from the metatable 
of the object at index -1, if there is one.


commit 5c02d723b8a6552bbd78deef6acd719bddf1e620
Author: Roman Khabibov <roman.habibov@tarantool.org>
Date:   Wed Dec 26 17:49:34 2018 +0300

     httpc: add checking of headers in httpc:request

     Add preprocessing of the request headers. Each header must be nil, 
'string' or 'table'
     with '__tostring' metamethod.

     Closes #3679

diff --git a/src/lua/httpc.c b/src/lua/httpc.c
index 5f4e2e912..d2a07c863 100644
--- a/src/lua/httpc.c
+++ b/src/lua/httpc.c
@@ -173,6 +173,15 @@ luaT_httpc_request(lua_State *L)
      if (!lua_isnil(L, -1)) {
          lua_pushnil(L);
          while (lua_next(L, -2) != 0) {
+            int header_type = lua_type(L, -1);
+            if (header_type != LUA_TSTRING) {
+                if (header_type != LUA_TTABLE) {
+                    return luaL_error(L, "can't convert header to a 
string");
+                } else if (!luaL_getmetafield(L, -1, "__tostring")) {
+                    return luaL_error(L, "can't convert header to a 
string");
+                }
+                lua_pop(L, 1);
+            }
              if (httpc_set_header(req, "%s: %s",
                           lua_tostring(L, -2),
                           lua_tostring(L, -1)) < 0) {
diff --git a/test/app-tap/http_client.test.lua 
b/test/app-tap/http_client.test.lua
index 10a3728f8..f231c2e90 100755
--- a/test/app-tap/http_client.test.lua
+++ b/test/app-tap/http_client.test.lua
@@ -205,6 +205,80 @@ 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 = 'can\'t convert header 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 +471,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

^ permalink raw reply	[flat|nested] 18+ messages in thread

* [tarantool-patches] Re: [PATCH] httpc: add checking of headers in httpc:request
  2019-01-31 23:47           ` Roman
@ 2019-02-05 11:42             ` Vladislav Shpilevoy
  2019-02-11 23:24               ` Roman
  0 siblings, 1 reply; 18+ messages in thread
From: Vladislav Shpilevoy @ 2019-02-05 11:42 UTC (permalink / raw)
  To: Roman, tarantool-patches, Alexander Turenko

Hi! Thanks for the fixes! See 3 comments below.

>> 4. In the commit message you said that 'nil' is ok, but here you did not
>> check for that and return an error. Of course, real nil can not appear in
>> a table, because it means absence of a value, but what about box.NULL?
> 
> Added test for this case, right?

1. Yes, you did, but the test fails. On my laptop http_client.test.lua
is totally broken for already a year at least, but I run the test from
console and got this:

     	tarantool> client = require('http.client')
	---
	...

	tarantool> http = client:new()
	---
	...

	tarantool> opts = {headers = {aaa = box.NULL}}
	---
	...

	tarantool> http:get('127.0.0.1:12345', opts)
	---
	- error: 'builtin/http.client.lua:299: can''t convert header to a string'

So box.NULL is not converted to string despite the fact that box.NULL == nil
and you said in the commit message

     "... Each header must be nil, 'string' or 'table' ..."

> 
> 
>> 5. Out of 80 symbols.

2. Still out of 80.

>>
>>> +                } else if (luaL_getmetafield(L, -1, "__tostring") == LUA_TNIL) {
> diff --git a/test/app-tap/http_client.test.lua b/test/app-tap/http_client.test.lua
> index 10a3728f8..f231c2e90 100755
> --- a/test/app-tap/http_client.test.lua
> +++ b/test/app-tap/http_client.test.lua
> @@ -205,6 +205,80 @@ 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

3. I am not a linguist, but the sentence looks grammatically
incorrect IMO. 'Allow only headers <something is missing> can be ... '.

> +local function test_request_headers(test, url, opts)
> +    local exp_err = 'can\'t convert header to a string'
> +    local cases = {
> +        {

^ permalink raw reply	[flat|nested] 18+ messages in thread

* [tarantool-patches] Re: [PATCH] httpc: add checking of headers in httpc:request
  2019-02-05 11:42             ` Vladislav Shpilevoy
@ 2019-02-11 23:24               ` Roman
  2019-02-15 21:17                 ` Vladislav Shpilevoy
  0 siblings, 1 reply; 18+ messages in thread
From: Roman @ 2019-02-11 23:24 UTC (permalink / raw)
  To: tarantool-patches; +Cc: Vladislav Shpilevoy

[-- Attachment #1: Type: text/plain, Size: 7273 bytes --]

Hi! Thanks for review.

On Вт, фев 5, 2019 at 2:42 PM, Vladislav Shpilevoy 
<v.shpilevoy@tarantool.org> wrote:
> Hi! Thanks for the fixes! See 3 comments below.
> 
>>> 4. In the commit message you said that 'nil' is ok, but here you 
>>> did not
>>> check for that and return an error. Of course, real nil can not 
>>> appear in
>>> a table, because it means absence of a value, but what about 
>>> box.NULL?
>> 
>> Added test for this case, right?
> 
> 1. Yes, you did, but the test fails. On my laptop http_client.test.lua
> is totally broken for already a year at least, but I run the test from
> console and got this:
> 
>     	tarantool> client = require('http.client')
> 	---
> 	...
> 
> 	tarantool> http = client:new()
> 	---
> 	...
> 
> 	tarantool> opts = {headers = {aaa = box.NULL}}
> 	---
> 	...
> 
> 	tarantool> http:get('127.0.0.1:12345', opts)
> 	---
> 	- error: 'builtin/http.client.lua:299: can''t convert header to a 
> string'
> 
> So box.NULL is not converted to string despite the fact that box.NULL 
> == nil
> and you said in the commit message
> 
>     "... Each header must be nil, 'string' or 'table' ..."
In a private chat, we decided that this result is ok. And I changed the 
commit message.

>>> 5. Out of 80 symbols.
> 
> 2. Still out of 80.
My text editor shows 70 symbols.

>>>> +                } else if (luaL_getmetafield(L, -1, "__tostring") 
>>>> == LUA_TNIL) {
>> diff --git a/test/app-tap/http_client.test.lua 
>> b/test/app-tap/http_client.test.lua
>> index 10a3728f8..f231c2e90 100755
>> --- a/test/app-tap/http_client.test.lua
>> +++ b/test/app-tap/http_client.test.lua
>> @@ -205,6 +205,80 @@ 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
> 
> 3. I am not a linguist, but the sentence looks grammatically
> incorrect IMO. 'Allow only headers <something is missing> can be ... 
> '.
> 
>> +local function test_request_headers(test, url, opts)
>> +    local exp_err = 'can\'t convert header to a string'
>> +    local cases = {
>> +        {
+				if (header_type != LUA_TTABLE) {
+					return luaL_error(L,
+						"headers must be string or table with \"__tostring\"");
+				} else if (!luaL_getmetafield(L, -1, "__tostring")) {
+					return luaL_error(L,
+						"headers must be string or table with \"__tostring\"");
+				}
Done like other error messages in this file. But I do not know how best 
to fit code in 80 symbols.


commit 104dbc419aaf3d9ec8f294513c73a98d2f103459
Author: Roman Khabibov <roman.habibov@tarantool.org>
Date:   Wed Dec 26 17:49:34 2018 +0300

    httpc: add checking of headers in httpc:request

    Add preprocessing of the request headers. Each header must be 
'string' or 'table'
    with '__tostring' metamethod.

    Closes #3679

diff --git a/src/lua/httpc.c b/src/lua/httpc.c
index 5f4e2e912..bc89a2334 100644
--- a/src/lua/httpc.c
+++ b/src/lua/httpc.c
@@ -173,6 +173,17 @@ luaT_httpc_request(lua_State *L)
 	if (!lua_isnil(L, -1)) {
 		lua_pushnil(L);
 		while (lua_next(L, -2) != 0) {
+			int header_type = lua_type(L, -1);
+			if (header_type != LUA_TSTRING) {
+				if (header_type != LUA_TTABLE) {
+					return luaL_error(L,
+						"headers must be string or table with \"__tostring\"");
+				} else if (!luaL_getmetafield(L, -1, "__tostring")) {
+					return luaL_error(L,
+						"headers must be string or table with \"__tostring\"");
+				}
+				lua_pop(L, 1);
+			}
 			if (httpc_set_header(req, "%s: %s",
 					     lua_tostring(L, -2),
 					     lua_tostring(L, -1)) < 0) {
diff --git a/test/app-tap/http_client.test.lua 
b/test/app-tap/http_client.test.lua
index 10a3728f8..8a98d1e92 100755
--- a/test/app-tap/http_client.test.lua
+++ b/test/app-tap/http_client.test.lua
@@ -205,6 +205,80 @@ 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 = 'headers must be string or table with "__tostring"'
+    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 +471,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



[-- Attachment #2: Type: text/html, Size: 8281 bytes --]

^ permalink raw reply	[flat|nested] 18+ messages in thread

* [tarantool-patches] Re: [PATCH] httpc: add checking of headers in httpc:request
  2019-02-11 23:24               ` Roman
@ 2019-02-15 21:17                 ` Vladislav Shpilevoy
  2019-02-19 10:49                   ` Roman
  0 siblings, 1 reply; 18+ messages in thread
From: Vladislav Shpilevoy @ 2019-02-15 21:17 UTC (permalink / raw)
  To: Roman, tarantool-patches

Hi!

>>         5. Out of 80 symbols. 
>>
>> 2. Still out of 80.
> My text editor shows 70 symbols.
> 

Then there is something wrong with your editor, sorry.

https://github.com/tarantool/tarantool/blob/romanhabibov/gh-3679-httpc-request/src/lua/httpc.c#L180

Lets count. Here you have 6 tabs - 6 * 8 = 48 symbols. Then you have 55 symbols of

     "headers must be string or table with \"__tostring\"");

48 + 55 = 103.

Also, the error string is not aligned by opening parenthesis.

In your email I see, that you have 4 symbols tab, what gives you 79 symbols, but
tab width in this file should be 8. So probably you mistakenly set tab width to
4 and the editor shows you < 80 width. Please, fix.

^ permalink raw reply	[flat|nested] 18+ messages in thread

* [tarantool-patches] Re: [PATCH] httpc: add checking of headers in httpc:request
  2019-02-15 21:17                 ` Vladislav Shpilevoy
@ 2019-02-19 10:49                   ` Roman
  2019-02-22 16:01                     ` Vladislav Shpilevoy
  0 siblings, 1 reply; 18+ messages in thread
From: Roman @ 2019-02-19 10:49 UTC (permalink / raw)
  To: tarantool-patches; +Cc: Vladislav Shpilevoy

[-- Attachment #1: Type: text/plain, Size: 5604 bytes --]


Hi! Thanks for review.

On Сб, фев 16, 2019 at 12:17 AM, Vladislav Shpilevoy 
<v.shpilevoy@tarantool.org> wrote:
> Hi!
> 
>>>         5. Out of 80 symbols. \x7f\x7f
>>> 2. Still out of 80.
>> My text editor shows 70 symbols.
>> 
> 
> Then there is something wrong with your editor, sorry.
> 
> https://github.com/tarantool/tarantool/blob/romanhabibov/gh-3679-httpc-request/src/lua/httpc.c#L180
> 
> Lets count. Here you have 6 tabs - 6 * 8 = 48 symbols. Then you have 
> 55 symbols of
> 
>     "headers must be string or table with \"__tostring\"");
> 
> 48 + 55 = 103.
> 
> Also, the error string is not aligned by opening parenthesis.
> 
> In your email I see, that you have 4 symbols tab, what gives you 79 
> symbols, but
> tab width in this file should be 8. So probably you mistakenly set 
> tab width to
> 4 and the editor shows you < 80 width. Please, fix.
> 
Ok. Done.

commit c9802592b27cb6986e3e72eb948d8dbfb21baa7e
Author: Roman Khabibov <roman.habibov@tarantool.org>
Date:   Wed Dec 26 17:49:34 2018 +0300

    httpc: add checking of headers in httpc:request

    Add preprocessing of the request headers. Each header must be 
'string' or 'table'
    with '__tostring' metamethod.

    Closes #3679

diff --git a/src/lua/httpc.c b/src/lua/httpc.c
index 5f4e2e912..fb012f58b 100644
--- a/src/lua/httpc.c
+++ b/src/lua/httpc.c
@@ -173,6 +173,18 @@ luaT_httpc_request(lua_State *L)
 	if (!lua_isnil(L, -1)) {
 		lua_pushnil(L);
 		while (lua_next(L, -2) != 0) {
+			int header_type = lua_type(L, -1);
+			if (header_type != LUA_TSTRING) {
+				char *err_msg = "headers must be " \
+				"string or table with \"__tostring\"";
+				if (header_type != LUA_TTABLE) {
+					return luaL_error(L, err_msg);
+				} else if (!luaL_getmetafield(L, -1,
+							      "__tostring")) {
+					return luaL_error(L, err_msg);
+				}
+				lua_pop(L, 1);
+			}
 			if (httpc_set_header(req, "%s: %s",
 					     lua_tostring(L, -2),
 					     lua_tostring(L, -1)) < 0) {
diff --git a/test/app-tap/http_client.test.lua 
b/test/app-tap/http_client.test.lua
index 10a3728f8..8a98d1e92 100755
--- a/test/app-tap/http_client.test.lua
+++ b/test/app-tap/http_client.test.lua
@@ -205,6 +205,80 @@ 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 = 'headers must be string or table with "__tostring"'
+    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 +471,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



[-- Attachment #2: Type: text/html, Size: 13142 bytes --]

^ permalink raw reply	[flat|nested] 18+ messages in thread

* [tarantool-patches] Re: [PATCH] httpc: add checking of headers in httpc:request
  2019-02-19 10:49                   ` Roman
@ 2019-02-22 16:01                     ` Vladislav Shpilevoy
  2019-02-23 21:38                       ` Roman Khabibov
  2019-02-23 22:43                       ` Roman Khabibov
  0 siblings, 2 replies; 18+ messages in thread
From: Vladislav Shpilevoy @ 2019-02-22 16:01 UTC (permalink / raw)
  To: Roman, tarantool-patches

Hi! Thanks for the fixes! See 3 comments below.

On 19/02/2019 13:49, Roman wrote:
> 
> Hi! Thanks for review.
> 
> On Сб, фев 16, 2019 at 12:17 AM, Vladislav Shpilevoy <v.shpilevoy@tarantool.org> wrote:
>> Hi!
>>
>>         5. Out of 80 symbols. \x7f\x7f 2. Still out of 80. 
>>
>>     My text editor shows 70 symbols. 
>>
>> Then there is something wrong with your editor, sorry. https://github.com/tarantool/tarantool/blob/romanhabibov/gh-3679-httpc-request/src/lua/httpc.c#L180 Lets count. Here you have 6 tabs - 6 * 8 = 48 symbols. Then you have 55 symbols of "headers must be string or table with \"__tostring\""); 48 + 55 = 103. Also, the error string is not aligned by opening parenthesis. In your email I see, that you have 4 symbols tab, what gives you 79 symbols, but tab width in this file should be 8. So probably you mistakenly set tab width to 4 and the editor shows you < 80 width. Please, fix. 
> Ok. Done.

1. Now you have leading whitespace on line 182. It is clearly visible in
git diff as a bright red point. Please, do self-review before sending
a patch.

> 
> commit c9802592b27cb6986e3e72eb948d8dbfb21baa7e
> Author: Roman Khabibov <roman.habibov@tarantool.org>
> Date: Wed Dec 26 17:49:34 2018 +0300
> 
> httpc: add checking of headers in httpc:request
> Add preprocessing of the request headers. Each header must be 'string' or 'table'
> with '__tostring' metamethod.
> Closes #3679
> 
> diff --git a/src/lua/httpc.c b/src/lua/httpc.c
> index 5f4e2e912..fb012f58b 100644
> --- a/src/lua/httpc.c
> +++ b/src/lua/httpc.c
> @@ -173,6 +173,18 @@ luaT_httpc_request(lua_State *L)
> if (!lua_isnil(L, -1)) {
> lua_pushnil(L);
> while (lua_next(L, -2) != 0) {
> + int header_type = lua_type(L, -1);
> + if (header_type != LUA_TSTRING) {
> + char *err_msg = "headers must be " \
> + "string or table with \"__tostring\"";

2. Please, do not assign a const char string to
a 'char *' variable. Use 'const char *'. Also,
please, properly align strings. This part should
look like this:

			if (header_type != LUA_TSTRING) {
				const char *err_msg =
					"headers must be string or table with "\
					"\"__tostring\"";
				if (header_type != LUA_TTABLE) {

Wrapped string is aligned by the beginning.

> + if (header_type != LUA_TTABLE) {
> + return luaL_error(L, err_msg);
> + } else if (!luaL_getmetafield(L, -1,
> + "__tostring")) {
> + return luaL_error(L, err_msg);
> + }
> + lua_pop(L, 1);
> + }

3. Obviously, there is still something wrong with indentation of your
editor/email agent/whatever else can remove tabs. Please, cope with it.
Before sending a next version into the list, send it to yourself and
check if now the indentation is good.

> if (httpc_set_header(req, "%s: %s",
> lua_tostring(L, -2),
> lua_tostring(L, -1)) < 0) {


^ permalink raw reply	[flat|nested] 18+ messages in thread

* [tarantool-patches] Re: [PATCH] httpc: add checking of headers in httpc:request
  2019-02-22 16:01                     ` Vladislav Shpilevoy
@ 2019-02-23 21:38                       ` Roman Khabibov
  2019-02-23 22:43                       ` Roman Khabibov
  1 sibling, 0 replies; 18+ messages in thread
From: Roman Khabibov @ 2019-02-23 21:38 UTC (permalink / raw)
  To: tarantool-patches; +Cc: v.shpilevoy

[-- Attachment #1: Type: text/plain, Size: 7370 bytes --]

Hi! Thanks for review.

On 2/22/19 7:01 PM, Vladislav Shpilevoy wrote:

> Hi! Thanks for the fixes! See 3 comments below.
> On 19/02/2019 13:49, Roman wrote:
>> Hi! Thanks for review.
>> On Сб, фев 16, 2019 at 12:17 AM, Vladislav Shpilevoy <v.shpilevoy@tarantool.org> wrote:
>>> Hi!
>>>          5. Out of 80 symbols. \x7f\x7f 2. Still out of 80.
>>>
>>>      My text editor shows 70 symbols.
>>>
>>> Then there is something wrong with your editor, sorry. https://github.com/tarantool/tarantool/blob/romanhabibov/gh-3679-httpc-request/src/lua/httpc.c#L180 Lets count. Here you have 6 tabs - 6 * 8 = 48 symbols. Then you have 55 symbols of "headers must be string or table with \"__tostring\""); 48 + 55 = 103. Also, the error string is not aligned by opening parenthesis. In your email I see, that you have 4 symbols tab, what gives you 79 symbols, but tab width in this file should be 8. So probably you mistakenly set tab width to 4 and the editor shows you < 80 width. Please, fix.
>> Ok. Done.
> 1. Now you have leading whitespace on line 182. It is clearly visible in
> git diff as a bright red point. Please, do self-review before sending
> a patch.

I'm sorry to waste your time. I was in a hurry and did't notice.


>> commit c9802592b27cb6986e3e72eb948d8dbfb21baa7e
>> Author: Roman Khabibov <roman.habibov@tarantool.org>
>> Date: Wed Dec 26 17:49:34 2018 +0300
>> httpc: add checking of headers in httpc:request
>> Add preprocessing of the request headers. Each header must be 'string' or 'table'
>> with '__tostring' metamethod.
>> Closes #3679
>> diff --git a/src/lua/httpc.c b/src/lua/httpc.c
>> index 5f4e2e912..fb012f58b 100644
>> --- a/src/lua/httpc.c
>> +++ b/src/lua/httpc.c
>> @@ -173,6 +173,18 @@ luaT_httpc_request(lua_State *L)
>> if (!lua_isnil(L, -1)) {
>> lua_pushnil(L);
>> while (lua_next(L, -2) != 0) {
>> + int header_type = lua_type(L, -1);
>> + if (header_type != LUA_TSTRING) {
>> + char *err_msg = "headers must be " \
>> + "string or table with \"__tostring\"";
> 2. Please, do not assign a const char string to
> a 'char *' variable. Use 'const char *'. Also,
> please, properly align strings. This part should
> look like this:
>              if (header_type != LUA_TSTRING) {
>                  const char *err_msg =
>                      "headers must be string or table with "\
>                      "\"__tostring\"";
>                  if (header_type != LUA_TTABLE) {
> Wrapped string is aligned by the beginning.
>> + if (header_type != LUA_TTABLE) {
>> + return luaL_error(L, err_msg);
>> + } else if (!luaL_getmetafield(L, -1,
>> + "__tostring")) {
>> + return luaL_error(L, err_msg);
>> + }
>> + lua_pop(L, 1);
>> + }

Done.

+				const char *err_msg =
+					"headers must be string or table "\
+					"with \"__tostring\"";


commit 42d25f4d6eef69ae56db3841863afa3373fa9b67
Author: Roman Khabibov <roman.habibov@tarantool.org>
Date:   Wed Dec 26 17:49:34 2018 +0300

     httpc: add checking of headers in httpc:request
     
     Add preprocessing of the request headers. Each header must be 'string' or 'table'
     with '__tostring' metamethod.
     
     Closes #3679

diff --git a/src/lua/httpc.c b/src/lua/httpc.c
index 5f4e2e912..db8634949 100644
--- a/src/lua/httpc.c
+++ b/src/lua/httpc.c
@@ -173,6 +173,19 @@ luaT_httpc_request(lua_State *L)
  	if (!lua_isnil(L, -1)) {
  		lua_pushnil(L);
  		while (lua_next(L, -2) != 0) {
+			int header_type = lua_type(L, -1);
+			if (header_type != LUA_TSTRING) {
+				const char *err_msg =
+					"headers must be string or table "\
+					"with \"__tostring\"";
+				if (header_type != LUA_TTABLE) {
+					return luaL_error(L, err_msg);
+				} else if (!luaL_getmetafield(L, -1,
+							      "__tostring")) {
+					return luaL_error(L, err_msg);
+				}
+				lua_pop(L, 1);
+			}
  			if (httpc_set_header(req, "%s: %s",
  					     lua_tostring(L, -2),
  					     lua_tostring(L, -1)) < 0) {
diff --git a/test/app-tap/http_client.test.lua b/test/app-tap/http_client.test.lua
index 10a3728f8..8a98d1e92 100755
--- a/test/app-tap/http_client.test.lua
+++ b/test/app-tap/http_client.test.lua
@@ -205,6 +205,80 @@ 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 = 'headers must be string or table with "__tostring"'
+    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 +471,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


[-- Attachment #2: Type: text/html, Size: 9552 bytes --]

^ permalink raw reply	[flat|nested] 18+ messages in thread

* [tarantool-patches] Re: [PATCH] httpc: add checking of headers in httpc:request
  2019-02-22 16:01                     ` Vladislav Shpilevoy
  2019-02-23 21:38                       ` Roman Khabibov
@ 2019-02-23 22:43                       ` Roman Khabibov
  2019-02-25 13:04                         ` Vladislav Shpilevoy
  1 sibling, 1 reply; 18+ messages in thread
From: Roman Khabibov @ 2019-02-23 22:43 UTC (permalink / raw)
  To: tarantool-patches; +Cc: Vladislav Shpilevoy

Hi! Thanks for review. 

On 2/22/19 7:01 PM, Vladislav Shpilevoy wrote: 


> Hi! Thanks for the fixes! See 3 comments below. 
> On 19/02/2019 13:49, Roman wrote: 
> 
>> Hi! Thanks for review. 
>> On Сб, фев 16, 2019 at 12:17 AM, Vladislav Shpilevoy 
>> <v.shpilevoy@tarantool.org> <mailto:v.shpilevoy@tarantool.org>
>>  wrote: 
>> 
>>> Hi! 
>>>          5. Out of 80 symbols. \x7f\x7f 2. Still out of 80. 
>>> 
>>>      My text editor shows 70 symbols. 
>>> 
>>> Then there is something wrong with your editor, sorry. 
>>> https://github.com/tarantool/tarantool/blob/romanhabibov/gh-3679-httpc-request/src/lua/httpc.c#L180 <https://github.com/tarantool/tarantool/blob/romanhabibov/gh-3679-httpc-request/src/lua/httpc.c#L180>
>>>  Lets count. Here you have 6 tabs - 6 * 8 = 48 symbols. Then you have 55 symbols of "headers must be string or table with \"__tostring\""); 48 + 55 = 103. Also, the error string is not aligned by opening parenthesis. In your email I see, that you have 4 symbols tab, what gives you 79 symbols, but tab width in this file should be 8. So probably you mistakenly set tab width to 4 and the editor shows you < 80 width. Please, fix. 
>>> 
>> Ok. Done. 
>> 
> 1. Now you have leading whitespace on line 182. It is clearly visible in 
> git diff as a bright red point. Please, do self-review before sending 
> a patch. 
> 
I'm sorry to waste your time. I was in a hurry and did't notice. 



>> commit c9802592b27cb6986e3e72eb948d8dbfb21baa7e 
>> Author: Roman Khabibov 
>> <roman.habibov@tarantool.org> <mailto:roman.habibov@tarantool.org>
>>  
>> Date: Wed Dec 26 17:49:34 2018 +0300 
>> httpc: add checking of headers in httpc:request 
>> Add preprocessing of the request headers. Each header must be 'string' or 'table' 
>> with '__tostring' metamethod. 
>> Closes #3679 
>> diff --git a/src/lua/httpc.c b/src/lua/httpc.c 
>> index 5f4e2e912..fb012f58b 100644 
>> --- a/src/lua/httpc.c 
>> +++ b/src/lua/httpc.c 
>> @@ -173,6 +173,18 @@ luaT_httpc_request(lua_State *L) 
>> if (!lua_isnil(L, -1)) { 
>> lua_pushnil(L); 
>> while (lua_next(L, -2) != 0) { 
>> + int header_type = lua_type(L, -1); 
>> + if (header_type != LUA_TSTRING) { 
>> + char *err_msg = "headers must be " \ 
>> + "string or table with \"__tostring\""; 
>> 
> 2. Please, do not assign a const char string to 
> a 'char *' variable. Use 'const char *'. Also, 
> please, properly align strings. This part should 
> look like this: 
>              if (header_type != LUA_TSTRING) { 
>                  const char *err_msg = 
>                      "headers must be string or table with "\ 
>                      "\"__tostring\""; 
>                  if (header_type != LUA_TTABLE) { 
> Wrapped string is aligned by the beginning. 
> 
>> + if (header_type != LUA_TTABLE) { 
>> + return luaL_error(L, err_msg); 
>> + } else if (!luaL_getmetafield(L, -1, 
>> + "__tostring")) { 
>> + return luaL_error(L, err_msg); 
>> + } 
>> + lua_pop(L, 1); 
>> + } 
>> 
Done.


commit 42d25f4d6eef69ae56db3841863afa3373fa9b67
Author: Roman Khabibov <roman.habibov@tarantool.org>
Date:   Wed Dec 26 17:49:34 2018 +0300

    httpc: add checking of headers in httpc:request
    
    Add preprocessing of the request headers. Each header must be 'string' or 'table'
    with '__tostring' metamethod.
    
    Closes #3679

diff --git a/src/lua/httpc.c b/src/lua/httpc.c
index 5f4e2e912..db8634949 100644
--- a/src/lua/httpc.c
+++ b/src/lua/httpc.c
@@ -173,6 +173,19 @@ luaT_httpc_request(lua_State *L)
 	if (!lua_isnil(L, -1)) {
 		lua_pushnil(L);
 		while (lua_next(L, -2) != 0) {
+			int header_type = lua_type(L, -1);
+			if (header_type != LUA_TSTRING) {
+				const char *err_msg =
+					"headers must be string or table "\
+					"with \"__tostring\"";
+				if (header_type != LUA_TTABLE) {
+					return luaL_error(L, err_msg);
+				} else if (!luaL_getmetafield(L, -1,
+							      "__tostring")) {
+					return luaL_error(L, err_msg);
+				}
+				lua_pop(L, 1);
+			}
 			if (httpc_set_header(req, "%s: %s",
 					     lua_tostring(L, -2),
 					     lua_tostring(L, -1)) < 0) {
diff --git a/test/app-tap/http_client.test.lua b/test/app-tap/http_client.test.lua
index 10a3728f8..8a98d1e92 100755
--- a/test/app-tap/http_client.test.lua
+++ b/test/app-tap/http_client.test.lua
@@ -205,6 +205,80 @@ 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 = 'headers must be string or table with "__tostring"'
+    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 +471,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

^ permalink raw reply	[flat|nested] 18+ messages in thread

* [tarantool-patches] Re: [PATCH] httpc: add checking of headers in httpc:request
  2019-02-23 22:43                       ` Roman Khabibov
@ 2019-02-25 13:04                         ` Vladislav Shpilevoy
  0 siblings, 0 replies; 18+ messages in thread
From: Vladislav Shpilevoy @ 2019-02-25 13:04 UTC (permalink / raw)
  To: Roman Khabibov, tarantool-patches, Kirill Yukhin

LGTM.

On 24/02/2019 01:43, Roman Khabibov wrote:
> Hi! Thanks for review.
> 
> On 2/22/19 7:01 PM, Vladislav Shpilevoy wrote:
> 
> 
>> Hi! Thanks for the fixes! See 3 comments below.
>> On 19/02/2019 13:49, Roman wrote:
>>
>>> Hi! Thanks for review.
>>> On Сб, фев 16, 2019 at 12:17 AM, Vladislav Shpilevoy
>>> <v.shpilevoy@tarantool.org> <mailto:v.shpilevoy@tarantool.org>
>>>   wrote:
>>>
>>>> Hi!
>>>>           5. Out of 80 symbols. \x7f\x7f 2. Still out of 80.
>>>>
>>>>       My text editor shows 70 symbols.
>>>>
>>>> Then there is something wrong with your editor, sorry.
>>>> https://github.com/tarantool/tarantool/blob/romanhabibov/gh-3679-httpc-request/src/lua/httpc.c#L180 <https://github.com/tarantool/tarantool/blob/romanhabibov/gh-3679-httpc-request/src/lua/httpc.c#L180>
>>>>   Lets count. Here you have 6 tabs - 6 * 8 = 48 symbols. Then you have 55 symbols of "headers must be string or table with \"__tostring\""); 48 + 55 = 103. Also, the error string is not aligned by opening parenthesis. In your email I see, that you have 4 symbols tab, what gives you 79 symbols, but tab width in this file should be 8. So probably you mistakenly set tab width to 4 and the editor shows you < 80 width. Please, fix.
>>>>
>>> Ok. Done.
>>>
>> 1. Now you have leading whitespace on line 182. It is clearly visible in
>> git diff as a bright red point. Please, do self-review before sending
>> a patch.
>>
> I'm sorry to waste your time. I was in a hurry and did't notice.
> 
> 
> 
>>> commit c9802592b27cb6986e3e72eb948d8dbfb21baa7e
>>> Author: Roman Khabibov
>>> <roman.habibov@tarantool.org> <mailto:roman.habibov@tarantool.org>
>>>   
>>> Date: Wed Dec 26 17:49:34 2018 +0300
>>> httpc: add checking of headers in httpc:request
>>> Add preprocessing of the request headers. Each header must be 'string' or 'table'
>>> with '__tostring' metamethod.
>>> Closes #3679
>>> diff --git a/src/lua/httpc.c b/src/lua/httpc.c
>>> index 5f4e2e912..fb012f58b 100644
>>> --- a/src/lua/httpc.c
>>> +++ b/src/lua/httpc.c
>>> @@ -173,6 +173,18 @@ luaT_httpc_request(lua_State *L)
>>> if (!lua_isnil(L, -1)) {
>>> lua_pushnil(L);
>>> while (lua_next(L, -2) != 0) {
>>> + int header_type = lua_type(L, -1);
>>> + if (header_type != LUA_TSTRING) {
>>> + char *err_msg = "headers must be " \
>>> + "string or table with \"__tostring\"";
>>>
>> 2. Please, do not assign a const char string to
>> a 'char *' variable. Use 'const char *'. Also,
>> please, properly align strings. This part should
>> look like this:
>>               if (header_type != LUA_TSTRING) {
>>                   const char *err_msg =
>>                       "headers must be string or table with "\
>>                       "\"__tostring\"";
>>                   if (header_type != LUA_TTABLE) {
>> Wrapped string is aligned by the beginning.
>>
>>> + if (header_type != LUA_TTABLE) {
>>> + return luaL_error(L, err_msg);
>>> + } else if (!luaL_getmetafield(L, -1,
>>> + "__tostring")) {
>>> + return luaL_error(L, err_msg);
>>> + }
>>> + lua_pop(L, 1);
>>> + }
>>>
> Done.
> 
> 
> commit 42d25f4d6eef69ae56db3841863afa3373fa9b67
> Author: Roman Khabibov <roman.habibov@tarantool.org>
> Date:   Wed Dec 26 17:49:34 2018 +0300
> 
>      httpc: add checking of headers in httpc:request
>      
>      Add preprocessing of the request headers. Each header must be 'string' or 'table'
>      with '__tostring' metamethod.
>      
>      Closes #3679
> 
> diff --git a/src/lua/httpc.c b/src/lua/httpc.c
> index 5f4e2e912..db8634949 100644
> --- a/src/lua/httpc.c
> +++ b/src/lua/httpc.c
> @@ -173,6 +173,19 @@ luaT_httpc_request(lua_State *L)
>   	if (!lua_isnil(L, -1)) {
>   		lua_pushnil(L);
>   		while (lua_next(L, -2) != 0) {
> +			int header_type = lua_type(L, -1);
> +			if (header_type != LUA_TSTRING) {
> +				const char *err_msg =
> +					"headers must be string or table "\
> +					"with \"__tostring\"";
> +				if (header_type != LUA_TTABLE) {
> +					return luaL_error(L, err_msg);
> +				} else if (!luaL_getmetafield(L, -1,
> +							      "__tostring")) {
> +					return luaL_error(L, err_msg);
> +				}
> +				lua_pop(L, 1);
> +			}
>   			if (httpc_set_header(req, "%s: %s",
>   					     lua_tostring(L, -2),
>   					     lua_tostring(L, -1)) < 0) {
> diff --git a/test/app-tap/http_client.test.lua b/test/app-tap/http_client.test.lua
> index 10a3728f8..8a98d1e92 100755
> --- a/test/app-tap/http_client.test.lua
> +++ b/test/app-tap/http_client.test.lua
> @@ -205,6 +205,80 @@ 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 = 'headers must be string or table with "__tostring"'
> +    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 +471,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
> 

^ permalink raw reply	[flat|nested] 18+ messages in thread

* [tarantool-patches] Re: [PATCH] httpc: add checking of headers in httpc:request
  2018-12-21 18:18 [tarantool-patches] [PATCH] httpc: add checking of headers in httpc:request Roman Khabibov
  2018-12-23  3:19 ` [tarantool-patches] " Alexander Turenko
@ 2019-02-25 14:51 ` Kirill Yukhin
  1 sibling, 0 replies; 18+ messages in thread
From: Kirill Yukhin @ 2019-02-25 14:51 UTC (permalink / raw)
  To: tarantool-patches; +Cc: alexander.turenko

Hello,

On 21 Dec 21:18, Roman Khabibov wrote:
> Add function that checks the value of each header. It must be 'string' or 'table'
> with '__tostring' metamethod.
> 
> Closes #3679
> ---
> 
> Branch: https://github.com/tarantool/tarantool/tree/romanhabibov/gh-3679-httpc-request
> Issue: https://github.com/tarantool/tarantool/issues/3679

I've checked your patch into 2.1 and 1.10 branch.

--
Regards, Kirill Yukhin

^ permalink raw reply	[flat|nested] 18+ messages in thread

end of thread, other threads:[~2019-02-25 14:51 UTC | newest]

Thread overview: 18+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2018-12-21 18:18 [tarantool-patches] [PATCH] httpc: add checking of headers in httpc:request Roman Khabibov
2018-12-23  3:19 ` [tarantool-patches] " Alexander Turenko
2018-12-26 15:56   ` Roman
2018-12-29 10:30     ` Vladislav Shpilevoy
2019-01-09 13:29       ` Roman
2019-01-24 14:54         ` Roman
2019-01-29 19:44         ` Vladislav Shpilevoy
2019-01-29 20:32           ` Alexander Turenko
2019-01-31 23:47           ` Roman
2019-02-05 11:42             ` Vladislav Shpilevoy
2019-02-11 23:24               ` Roman
2019-02-15 21:17                 ` Vladislav Shpilevoy
2019-02-19 10:49                   ` Roman
2019-02-22 16:01                     ` Vladislav Shpilevoy
2019-02-23 21:38                       ` Roman Khabibov
2019-02-23 22:43                       ` Roman Khabibov
2019-02-25 13:04                         ` Vladislav Shpilevoy
2019-02-25 14:51 ` Kirill Yukhin

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox