From 4cbf7b66252e48c9d986a673ad27ce5fd7841169 Mon Sep 17 00:00:00 2001 From: James Hurst Date: Tue, 16 Mar 2021 12:06:24 +0000 Subject: [PATCH 1/9] Ensure request body Content-Length is set correctly (#230) * Avoid sending non-string body values Fixes #217 * Add newline at EOF * Document support for table of strings in request body * Revert change preventing non string request bodies * Add tests for different request body types * Ensure request body Content-Length is set correctly * Don't blindly set request content length * Add more explicit support for chunked request body iterators * Use new transfer encoding utility * Add tests for client body reader Fails on chunked input until supported by ngx_lua * Improve transfer encoding check Co-authored-by: Thijs Schreijer Co-authored-by: Thijs Schreijer --- README.md | 2 +- lib/resty/http.lua | 65 +++++++++++------ t/02-chunked.t | 32 ++++++++ t/03-requestbody.t | 158 ++++++++++++++++++++++++++++++++++++++++ t/10-clientbodyreader.t | 56 ++++++++++++++ 5 files changed, 288 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index c9fe91b7..a1d233df 100644 --- a/README.md +++ b/README.md @@ -239,7 +239,7 @@ The `params` table expects the following fields: * `path`: The path string. Defaults to `/`. * `query`: The query string, presented as either a literal string or Lua table.. * `headers`: A table of request headers. -* `body`: The request body as a string, or an iterator function (see [get\_client\_body\_reader](#get_client_body_reader)). +* `body`: The request body as a string, a table of strings, or an iterator function yielding strings until nil when exhausted. Note that you must specify a `Content-Length` for the request body, or specify `Transfer-Encoding: chunked` and have your function implement the encoding. See also: [get\_client\_body\_reader](#get_client_body_reader)). When the request is successful, `res` will contain the following fields: diff --git a/lib/resty/http.lua b/lib/resty/http.lua index 5e3e4a88..98acb8ef 100644 --- a/lib/resty/http.lua +++ b/lib/resty/http.lua @@ -398,6 +398,23 @@ local function _receive_headers(sock) end +local function transfer_encoding_is_chunked(headers) + local te = headers["Transfer-Encoding"] + if not te then + return false + end + + -- Handle duplicate headers + -- This shouldn't happen but can in the real world + if type(te) ~= "string" then + te = tbl_concat(te, ",") + end + + return str_find(str_lower(te), "chunked", 1, true) ~= nil +end +_M.transfer_encoding_is_chunked = transfer_encoding_is_chunked + + local function _chunked_body_reader(sock, default_chunk_size) return co_wrap(function(max_chunk_size) local remaining = 0 @@ -575,7 +592,7 @@ end local function _send_body(sock, body) - if type(body) == 'function' then + if type(body) == "function" then repeat local chunk, err, partial = body() @@ -627,12 +644,13 @@ function _M.send_request(self, params) local body = params.body local headers = http_headers.new() - local params_headers = params.headers or {} - -- We assign one by one so that the metatable can handle case insensitivity + -- We assign one-by-one so that the metatable can handle case insensitivity -- for us. You can blame the spec for this inefficiency. + local params_headers = params.headers or {} for k, v in pairs(params_headers) do headers[k] = v end + if not headers["Proxy-Authorization"] then -- TODO: next major, change this to always override the provided -- header. Can't do that yet because it would be breaking. @@ -644,12 +662,28 @@ function _M.send_request(self, params) -- Ensure minimal headers are set if not headers["Content-Length"] then - if type(body) == 'string' then - headers["Content-Length"] = #body + local body_type = type(body) + + if body_type == "function" then + if not transfer_encoding_is_chunked(headers) then + return nil, "Request body is a function but a length or chunked encoding is not specified" + end + + elseif body_type == "table" then + local length = 0 + for _, v in ipairs(body) do + length = length + #tostring(v) + end + headers["Content-Length"] = length + elseif body == nil and EXPECTING_BODY[str_upper(params.method)] then headers["Content-Length"] = 0 + + elseif body ~= nil then + headers["Content-Length"] = #tostring(body) end end + if not headers["Host"] then if (str_sub(self.host, 1, 5) == "unix:") then return nil, "Unable to generate a useful Host header for a unix domain socket. Please provide one." @@ -751,22 +785,8 @@ function _M.read_response(self, params) if _should_receive_body(params.method, status) then has_body = true - local te = res_headers["Transfer-Encoding"] - - -- Handle duplicate headers - -- This shouldn't happen but can in the real world - if type(te) == "table" then - te = tbl_concat(te, "") - end - - local ok, encoding = pcall(str_lower, te) - if not ok then - encoding = "" - end - - if version == 1.1 and str_find(encoding, "chunked", 1, true) ~= nil then + if version == 1.1 and transfer_encoding_is_chunked(res_headers) then body_reader, err = _chunked_body_reader(sock) - else local ok, length = pcall(tonumber, res_headers["Content-Length"]) if not ok then @@ -775,9 +795,7 @@ function _M.read_response(self, params) end body_reader, err = _body_reader(sock, length) - end - end if res_headers["Trailer"] then @@ -941,10 +959,9 @@ function _M.get_client_body_reader(_, chunksize, sock) local headers = ngx_req_get_headers() local length = headers.content_length - local encoding = headers.transfer_encoding if length then return _body_reader(sock, tonumber(length), chunksize) - elseif encoding and str_lower(encoding) == 'chunked' then + elseif transfer_encoding_is_chunked(headers) then -- Not yet supported by ngx_lua but should just work... return _chunked_body_reader(sock, chunksize) else diff --git a/t/02-chunked.t b/t/02-chunked.t index dda47c4b..92d0ecc0 100644 --- a/t/02-chunked.t +++ b/t/02-chunked.t @@ -238,3 +238,35 @@ table --- no_error_log [error] [warn] + + +=== TEST 5: transfer_encoding_is_chunked utility. +--- http_config eval: $::HttpConfig +--- config + location = /a { + content_by_lua_block { + local http_headers = require("resty.http_headers") + local http = require("resty.http") + + local headers = http_headers:new() + assert(http.transfer_encoding_is_chunked(headers) == false, + "empty headers should return false") + + headers["Transfer-Encoding"] = "chunked" + assert(http.transfer_encoding_is_chunked(headers) == true, + "te set to `chunked` should return true`") + + headers["Transfer-Encoding"] = " ChuNkEd " + assert(http.transfer_encoding_is_chunked(headers) == true, + "te set to ` ChuNkEd ` should return true`") + + headers["Transfer-Encoding"] = { "chunked", " ChuNkEd " } + assert(http.transfer_encoding_is_chunked(headers) == true, + "te set to table values containing `chunked` should return true`") + } + } +--- request +GET /a +--- no_error_log +[error] +[warn] diff --git a/t/03-requestbody.t b/t/03-requestbody.t index 1e28c83a..c1fdd241 100644 --- a/t/03-requestbody.t +++ b/t/03-requestbody.t @@ -203,3 +203,161 @@ Expectation Failed --- no_error_log [error] [warn] + + +=== TEST 5: Non string request bodies are converted with correct length +--- http_config eval: $::HttpConfig +--- config + location = /a { + content_by_lua ' + local httpc = require("resty.http").new() + + local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/b" + + for _, body in ipairs({ 12345, + true, + "string", + { "tab", "le" }, + { "mix", 123, "ed", "tab", "le" } }) do + + local res, err = assert(httpc:request_uri(uri, { + body = body, + })) + + ngx.say(res.body) + end + '; + } + location = /b { + content_by_lua ' + ngx.req.read_body() + ngx.print(ngx.req.get_body_data()) + '; + } +--- request +GET /a +--- response_body +12345 +true +string +table +mix123edtable +--- no_error_log +[error] +[warn] + + +=== TEST 6: Request body as iterator +--- http_config eval: $::HttpConfig +--- config + location = /a { + content_by_lua ' + local httpc = require("resty.http").new() + + local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/b" + + local res, err = assert(httpc:request_uri(uri, { + body = coroutine.wrap(function() + coroutine.yield("foo") + coroutine.yield("bar") + end), + headers = { + ["Content-Length"] = 6 + } + })) + + ngx.say(res.body) + '; + } + location = /b { + content_by_lua ' + ngx.req.read_body() + ngx.print(ngx.req.get_body_data()) + '; + } +--- request +GET /a +--- response_body +foobar +--- no_error_log +[error] +[warn] + + +=== TEST 7: Request body as iterator, errors with missing length +--- http_config eval: $::HttpConfig +--- config + location = /a { + content_by_lua ' + local httpc = require("resty.http").new() + + local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/b" + + local res, err = httpc:request_uri(uri, { + body = coroutine.wrap(function() + coroutine.yield("foo") + coroutine.yield("bar") + end), + }) + + assert(not res) + ngx.say(err) + '; + } + location = /b { + content_by_lua ' + ngx.req.read_body() + ngx.print(ngx.req.get_body_data()) + '; + } +--- request +GET /a +--- response_body +Request body is a function but a length or chunked encoding is not specified +--- no_error_log +[error] +[warn] + + +=== TEST 8: Request body as iterator with chunked encoding +--- http_config eval: $::HttpConfig +--- config + location = /a { + content_by_lua_block { + local httpc = require("resty.http").new() + local yield = coroutine.yield + + local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/b" + + local res, err = assert(httpc:request_uri(uri, { + body = coroutine.wrap(function() + yield("3\r\n") + yield("foo\r\n") + + yield("3\r\n") + yield("bar\r\n") + + yield("0\r\n") + yield("\r\n") + end), + headers = { + ["Transfer-Encoding"] = "chunked" + } + })) + + ngx.say(res.body) + } + } + location = /b { + content_by_lua ' + ngx.req.read_body() + ngx.print(ngx.req.get_body_data()) + '; + } +--- request +GET /a +--- response_body +foobar +--- no_error_log +[error] +[warn] diff --git a/t/10-clientbodyreader.t b/t/10-clientbodyreader.t index 21d0b1e0..6380c4a0 100644 --- a/t/10-clientbodyreader.t +++ b/t/10-clientbodyreader.t @@ -64,3 +64,59 @@ OK [error] [warn] + +=== TEST 2: Read request body +--- http_config eval: $::HttpConfig +--- config + location = /a { + content_by_lua_block { + local httpc = require("resty.http").new() + + local reader, err = assert(httpc:get_client_body_reader()) + + repeat + local buffer, err = reader() + if err then + ngx.log(ngx.ERR, err) + end + + if buffer then + ngx.print(buffer) + end + until not buffer + } + } +--- request +POST /a +foobar +--- response_body: foobar +--- no_error_log +[error] +[warn] + + +=== TEST 2: Read chunked request body, errors as not yet supported +--- http_config eval: $::HttpConfig +--- config + location = /a { + content_by_lua_block { + local httpc = require("resty.http").new() + local _, err = httpc:get_client_body_reader() + ngx.log(ngx.ERR, err) + } + } +--- more_headers +Transfer-Encoding: chunked +--- request eval +"POST /a +3\r +foo\r +3\r +bar\r +0\r +\r +" +--- error_log +chunked request bodies not supported yet +--- no_error_log +[warn] From d83df5df6e658dac5dddb0bda7be176688a468b6 Mon Sep 17 00:00:00 2001 From: James Hurst Date: Tue, 23 Mar 2021 10:17:59 +0000 Subject: [PATCH 2/9] Allow actions to run on pr as well as push events (#233) Also removed avoiding builds on doc only, since checks are required for merging. --- .github/workflows/test.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e4d5dbce..7708e04a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,9 +1,6 @@ name: Test -on: - push: - paths-ignore: # Skip if only docs are updated - - '*.md' +on: [push, pull_request] jobs: luacheck: From 1120af703db606045c36c6832033522f846820f6 Mon Sep 17 00:00:00 2001 From: Thijs Schreijer Date: Tue, 23 Mar 2021 01:57:02 +0100 Subject: [PATCH 3/9] fix(deprecated) do not use deprecated method --- lib/resty/http_connect.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/resty/http_connect.lua b/lib/resty/http_connect.lua index ffd72af4..cbb102ac 100644 --- a/lib/resty/http_connect.lua +++ b/lib/resty/http_connect.lua @@ -213,7 +213,7 @@ local function connect(self, options) -- Now do the ssl handshake if ssl and sock:getreusedtimes() == 0 then - local ok, err = self:ssl_handshake(nil, ssl_server_name, ssl_verify, ssl_send_status_req) + local ok, err = sock:sslhandshake(nil, ssl_server_name, ssl_verify, ssl_send_status_req) if not ok then self:close() return nil, err From d7f636c28c12cd5ec9a5c033f0c99f9ee943c450 Mon Sep 17 00:00:00 2001 From: James Hurst Date: Mon, 29 Mar 2021 11:38:38 +0100 Subject: [PATCH 4/9] fix/drop content length when chunked (#234) * Make flakey test a little less flakey * Ensure Content-Length is dropped if Transfer-Encoding is specified https://tools.ietf.org/html/rfc7230#section-3.3.3 --- lib/resty/http.lua | 41 ++++++++++++++++++++++-------------- t/02-chunked.t | 52 ++++++++++++++++++++++++++++++++++++++++++++++ t/14-host-header.t | 1 + 3 files changed, 78 insertions(+), 16 deletions(-) diff --git a/lib/resty/http.lua b/lib/resty/http.lua index 98acb8ef..058f5a74 100644 --- a/lib/resty/http.lua +++ b/lib/resty/http.lua @@ -659,28 +659,37 @@ function _M.send_request(self, params) headers["Proxy-Authorization"] = self.http_proxy_auth end - -- Ensure minimal headers are set + -- Ensure we have appropriate message length or encoding. + do + local is_chunked = transfer_encoding_is_chunked(headers) + + if is_chunked then + -- If we have both Transfer-Encoding and Content-Length we MUST + -- drop the Content-Length, to help prevent request smuggling. + -- https://tools.ietf.org/html/rfc7230#section-3.3.3 + headers["Content-Length"] = nil - if not headers["Content-Length"] then - local body_type = type(body) + elseif not headers["Content-Length"] then + -- A length was not given, try to calculate one. - if body_type == "function" then - if not transfer_encoding_is_chunked(headers) then + local body_type = type(body) + + if body_type == "function" then return nil, "Request body is a function but a length or chunked encoding is not specified" - end - elseif body_type == "table" then - local length = 0 - for _, v in ipairs(body) do - length = length + #tostring(v) - end - headers["Content-Length"] = length + elseif body_type == "table" then + local length = 0 + for _, v in ipairs(body) do + length = length + #tostring(v) + end + headers["Content-Length"] = length - elseif body == nil and EXPECTING_BODY[str_upper(params.method)] then - headers["Content-Length"] = 0 + elseif body == nil and EXPECTING_BODY[str_upper(params.method)] then + headers["Content-Length"] = 0 - elseif body ~= nil then - headers["Content-Length"] = #tostring(body) + elseif body ~= nil then + headers["Content-Length"] = #tostring(body) + end end end diff --git a/t/02-chunked.t b/t/02-chunked.t index 92d0ecc0..26b75f23 100644 --- a/t/02-chunked.t +++ b/t/02-chunked.t @@ -263,10 +263,62 @@ table headers["Transfer-Encoding"] = { "chunked", " ChuNkEd " } assert(http.transfer_encoding_is_chunked(headers) == true, "te set to table values containing `chunked` should return true`") + + headers["Transfer-Encoding"] = "chunked" + headers["Content-Length"] = 10 + assert(http.transfer_encoding_is_chunked(headers) == true, + "transfer encoding should override content-length`") + } + } +--- request +GET /a +--- no_error_log +[error] +[warn] + + +=== TEST 6: Don't send Content-Length if Transfer-Encoding is specified +--- http_config eval: $::HttpConfig +--- config + location = /a { + content_by_lua_block { + local httpc = require("resty.http").new() + local yield = coroutine.yield + + local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/b" + + local res, err = assert(httpc:request_uri(uri, { + body = coroutine.wrap(function() + yield("3\r\n") + yield("foo\r\n") + + yield("3\r\n") + yield("bar\r\n") + + yield("0\r\n") + yield("\r\n") + end), + headers = { + ["Transfer-Encoding"] = "chunked", + ["Content-Length"] = 42, + }, + })) + + ngx.say(res.body) + } + } + location = /b { + content_by_lua_block { + ngx.req.read_body() + ngx.say(ngx.req.get_headers()["Content-Length"]) + ngx.print(ngx.req.get_body_data()) } } --- request GET /a +--- response_body +nil +foobar --- no_error_log [error] [warn] diff --git a/t/14-host-header.t b/t/14-host-header.t index 685813c0..39e4d3ad 100644 --- a/t/14-host-header.t +++ b/t/14-host-header.t @@ -67,6 +67,7 @@ Host: www.google.com local http = require "resty.http" local httpc = http.new() + httpc:set_timeouts(300, 1000, 1000) local res, err = httpc:request_uri("https://www.google.com:443", { ssl_verify = false }) '; } From faf7b45f27588af98e409e6870cb59691cea13f3 Mon Sep 17 00:00:00 2001 From: James Hurst Date: Tue, 6 Apr 2021 13:20:23 +0100 Subject: [PATCH 5/9] release/0.16 (#235) * Bump version * Don't ignore luarock spec files --- .gitignore | 2 +- lib/resty/http.lua | 2 +- lib/resty/http_headers.lua | 2 +- ...sty-http-0.15-0.rockspec => lua-resty-http-0.16-0.rockspec | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) rename lua-resty-http-0.15-0.rockspec => lua-resty-http-0.16-0.rockspec (94%) diff --git a/.gitignore b/.gitignore index 51b26b94..422ea976 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ t/servroot/ t/error.log -lua-resty-http* luacov* +*.src.rock diff --git a/lib/resty/http.lua b/lib/resty/http.lua index 058f5a74..7f4dbc3a 100644 --- a/lib/resty/http.lua +++ b/lib/resty/http.lua @@ -106,7 +106,7 @@ end local _M = { - _VERSION = '0.14', + _VERSION = '0.16', } _M._USER_AGENT = "lua-resty-http/" .. _M._VERSION .. " (Lua) ngx_lua/" .. ngx.config.ngx_lua_version diff --git a/lib/resty/http_headers.lua b/lib/resty/http_headers.lua index 6771d1ed..7b13ba3a 100644 --- a/lib/resty/http_headers.lua +++ b/lib/resty/http_headers.lua @@ -4,7 +4,7 @@ local rawget, rawset, setmetatable = local str_lower = string.lower local _M = { - _VERSION = '0.14', + _VERSION = '0.16', } diff --git a/lua-resty-http-0.15-0.rockspec b/lua-resty-http-0.16-0.rockspec similarity index 94% rename from lua-resty-http-0.15-0.rockspec rename to lua-resty-http-0.16-0.rockspec index 71ba0abe..5e79ae93 100644 --- a/lua-resty-http-0.15-0.rockspec +++ b/lua-resty-http-0.16-0.rockspec @@ -1,8 +1,8 @@ package = "lua-resty-http" -version = "0.15-0" +version = "0.16-0" source = { url = "git://github.com/ledgetech/lua-resty-http", - tag = "v0.15" + tag = "v0.16" } description = { summary = "Lua HTTP client cosocket driver for OpenResty / ngx_lua.", From 865b4c6e96dc85beb64ee1482dd6f1953c3d3356 Mon Sep 17 00:00:00 2001 From: Thijs Schreijer Date: Fri, 9 Apr 2021 18:02:16 +0200 Subject: [PATCH 6/9] fix(request_uri) use uri host for ssl-server-name (#237) also fixes an incorrect note in the docs fixes #236 --- README.md | 2 +- lib/resty/http.lua | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a1d233df..d6ef64bc 100644 --- a/README.md +++ b/README.md @@ -255,7 +255,7 @@ When the request is successful, `res` will contain the following fields: `syntax: res, err = httpc:request_uri(uri, params)` -The single-shot interface (see [usage](#Usage)). Since this method performs an entire end-to-end request, options specified in the `params` can include anything found in both [connect](#connect) and [request](#request) documented above. Note also that fields in `params` will override relevant components of the `uri` if specified. +The single-shot interface (see [usage](#Usage)). Since this method performs an entire end-to-end request, options specified in the `params` can include anything found in both [connect](#connect) and [request](#request) documented above. Note also that fields `path`, and `query`, in `params` will override relevant components of the `uri` if specified (`scheme`, `host`, and `port` will always be taken from the `uri`). There are 3 additional parameters for controlling keepalives: diff --git a/lib/resty/http.lua b/lib/resty/http.lua index 7f4dbc3a..46386fb7 100644 --- a/lib/resty/http.lua +++ b/lib/resty/http.lua @@ -899,6 +899,7 @@ function _M.request_uri(self, uri, params) params.scheme, params.host, params.port, path, query = unpack(parsed_uri) params.path = params.path or path params.query = params.query or query + params.ssl_server_name = params.ssl_server_name or params.host end do From 9bf951dfe162dd9710a0e1f4525738d4902e9d20 Mon Sep 17 00:00:00 2001 From: James Hurst Date: Fri, 9 Apr 2021 17:11:35 +0100 Subject: [PATCH 7/9] release/0.16.1 (#238) * Ignore luarocks build artefacts * Bump version to 1.16.1 --- .gitignore | 4 +++- lib/resty/http.lua | 2 +- lib/resty/http_headers.lua | 2 +- ...y-http-0.16-0.rockspec => lua-resty-http-0.16.1-0.rockspec | 4 ++-- 4 files changed, 7 insertions(+), 5 deletions(-) rename lua-resty-http-0.16-0.rockspec => lua-resty-http-0.16.1-0.rockspec (93%) diff --git a/.gitignore b/.gitignore index 422ea976..44834f8e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ t/servroot/ t/error.log luacov* -*.src.rock +lua-resty-http-*/ +lua-resty-http-*.src.rock +lua-resty-http-*.tar.gz diff --git a/lib/resty/http.lua b/lib/resty/http.lua index 46386fb7..85d83c12 100644 --- a/lib/resty/http.lua +++ b/lib/resty/http.lua @@ -106,7 +106,7 @@ end local _M = { - _VERSION = '0.16', + _VERSION = '0.16.1', } _M._USER_AGENT = "lua-resty-http/" .. _M._VERSION .. " (Lua) ngx_lua/" .. ngx.config.ngx_lua_version diff --git a/lib/resty/http_headers.lua b/lib/resty/http_headers.lua index 7b13ba3a..d9272da5 100644 --- a/lib/resty/http_headers.lua +++ b/lib/resty/http_headers.lua @@ -4,7 +4,7 @@ local rawget, rawset, setmetatable = local str_lower = string.lower local _M = { - _VERSION = '0.16', + _VERSION = '0.16.1', } diff --git a/lua-resty-http-0.16-0.rockspec b/lua-resty-http-0.16.1-0.rockspec similarity index 93% rename from lua-resty-http-0.16-0.rockspec rename to lua-resty-http-0.16.1-0.rockspec index 5e79ae93..22726ffa 100644 --- a/lua-resty-http-0.16-0.rockspec +++ b/lua-resty-http-0.16.1-0.rockspec @@ -1,8 +1,8 @@ package = "lua-resty-http" -version = "0.16-0" +version = "0.16.1-0" source = { url = "git://github.com/ledgetech/lua-resty-http", - tag = "v0.16" + tag = "v0.16.1" } description = { summary = "Lua HTTP client cosocket driver for OpenResty / ngx_lua.", From f754eb7ecc8c969657b357ae82f93a05a29dd395 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=B6=E3=83=BC=E3=81=A8=20/=20Yoshiaki=20Ueda?= Date: Sat, 24 Apr 2021 00:26:09 +0900 Subject: [PATCH 8/9] fix readme (#239) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d6ef64bc..12baa6bc 100644 --- a/README.md +++ b/README.md @@ -93,7 +93,7 @@ local httpc = require("resty.http").new() -- First establish a connection local ok, err = httpc:connect({ scheme = "https", - host = "127.0.0.1" + host = "127.0.0.1", port = 8080, }) if not ok then From 0ce55d6d15da140ecc5966fa848204c6fd9074e8 Mon Sep 17 00:00:00 2001 From: Arthur Khashaev Date: Fri, 6 Aug 2021 12:14:38 +0300 Subject: [PATCH 9/9] Add error message for broken response status line (#242) * Add test to reproduce #241 * Add error message for broken response status line Fixes #241 * Add test for an explicit error message string See #241 --- lib/resty/http.lua | 16 +++++++++++++++- t/01-basic.t | 48 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 1 deletion(-) diff --git a/lib/resty/http.lua b/lib/resty/http.lua index 85d83c12..5f7ed1af 100644 --- a/lib/resty/http.lua +++ b/lib/resty/http.lua @@ -362,7 +362,21 @@ local function _receive_status(sock) return nil, nil, nil, err end - return tonumber(str_sub(line, 10, 12)), tonumber(str_sub(line, 6, 8)), str_sub(line, 14) + local version = tonumber(str_sub(line, 6, 8)) + if not version then + return nil, nil, nil, + "couldn't parse HTTP version from response status line: " .. line + end + + local status = tonumber(str_sub(line, 10, 12)) + if not status then + return nil, nil, nil, + "couldn't parse status code from response status line: " .. line + end + + local reason = str_sub(line, 14) + + return status, version, reason end diff --git a/t/01-basic.t b/t/01-basic.t index 0439a8a0..f3c413d7 100644 --- a/t/01-basic.t +++ b/t/01-basic.t @@ -341,3 +341,51 @@ OK --- no_error_log [error] [warn] + +=== TEST 13: Should return error on invalid HTTP version in response status line +--- http_config eval: $::HttpConfig +--- config + location = /a { + content_by_lua_block { + local http = require "resty.http" + local httpc = http.new() + local res, err = httpc:request_uri("http://127.0.0.1:12345") + + assert(err == "couldn't parse HTTP version from response status line: TEAPOT/1.1 OMG") + } + } +--- tcp_listen: 12345 +--- tcp_reply +TEAPOT/1.1 OMG +Server: Teapot + +OK +--- request +GET /a +--- no_error_log +[error] +[warn] + +=== TEST 14: Should return error on invalid status code in response status line +--- http_config eval: $::HttpConfig +--- config + location = /a { + content_by_lua_block { + local http = require "resty.http" + local httpc = http.new() + local res, err = httpc:request_uri("http://127.0.0.1:12345") + + assert(err == "couldn't parse status code from response status line: HTTP/1.1 OMG") + } + } +--- tcp_listen: 12345 +--- tcp_reply +HTTP/1.1 OMG +Server: Teapot + +OK +--- request +GET /a +--- no_error_log +[error] +[warn]