[patches] [http 1/1] http: Add cookie parser
imarkov
imarkov at tarantool.org
Wed Feb 14 14:44:17 MSK 2018
* Add parser of cookies from 'Set-Cookie' header.
* All cookies are stored in response object in cookie table.
* The format of each table element is following:
resp[key] = {value, {option, ...}},
where key, value is cookie key, value
options are array of strings, set-cookie header options,
e.g. HttpOnly, Expires=<date>.
Closes #2801
Signed-off-by: imarkov <imarkov at tarantool.org>
---
src/lua/httpc.lua | 116 ++++++++++++++++++++++++++++++++++++++
test/app-tap/http_client.test.lua | 15 ++++-
test/app-tap/httpd.py | 7 ++-
3 files changed, 134 insertions(+), 4 deletions(-)
diff --git a/src/lua/httpc.lua b/src/lua/httpc.lua
index 3ddd3e7..cd44b60 100644
--- a/src/lua/httpc.lua
+++ b/src/lua/httpc.lua
@@ -103,6 +103,119 @@ local special_headers = {
["user-agent"] = true,
}
+local special_characters = {
+ ['('] = true,
+ [')'] = true,
+ ['['] = true,
+ [']'] = true,
+ ['<'] = true,
+ ['>'] = true,
+ ['>'] = true,
+ ['@'] = true,
+ [','] = true,
+ [';'] = true,
+ [':'] = true,
+ ['\\'] = true,
+ ['\"'] = true,
+ ['/'] = true,
+ ['?'] = true,
+ ['='] = true,
+ ['{'] = true,
+ ['}'] = true,
+ [' '] = true,
+ ['\t'] = true
+}
+
+local option_keys = {
+ ["Expires"] = true,
+ ["Max-Age"] = true,
+ ["Domain"] = true,
+ ["Path"] = true,
+ ["Secure"] = true,
+ ["HttpOnly"] = true,
+ ["SameSite"] = true,
+}
+
+--local function process_set_cookies(value, result)
+-- local key_start, value_start
+-- local key, val
+-- local symbols = value:gmatch('.')
+-- local options = {}
+-- local cur = 0
+-- for v in symbols do
+-- cur = cur + 1
+-- if v == ' ' or v == '\t' then
+-- goto continue
+-- end
+-- key_start = cur
+-- -- parse cookie name
+-- while not special_characters[v] do
+-- if v == nil then
+-- return
+-- end
+-- v = symbols()
+-- cur = cur + 1
+-- end
+-- key = value:sub(key_start, cur)
+-- if not v or v ~= '=' then
+-- -- invalid header
+-- return
+-- end
+-- while v == ' ' do
+-- v = symbols()
+-- cur = cur + 1
+-- end
+--
+-- if v == nil then
+-- return
+-- end
+--
+-- while v and v ~= ';' do
+-- if v == nil then
+-- break
+-- end
+-- v = symbols()
+-- cur = cur + 1
+-- end
+--
+-- result[key] = {val, options}
+-- ::continue::
+-- end
+--end
+
+local function process_cookie(cookie, result)
+ local vals = cookie:split(';')
+ local val = vals[1]:split('=')
+ if #val < 2 then
+ return
+ end
+ val[1] = string.strip(val[1])
+ for c in val[1]:gmatch('.') do
+ if special_characters[c] then
+ return
+ end
+ end
+
+ local options = {}
+ table.remove(vals, 1)
+ for _, opt in pairs(vals) do
+ local splitted = opt:split('=')
+ splitted = string.strip(splitted[1])
+ if option_keys[splitted] then
+ table.insert(options, string.strip(opt))
+ end
+ end
+ result[val[1]] = {string.strip(val[2]), options}
+end
+
+local function process_cookies(cookies)
+ local result = {}
+ for _, val in pairs(cookies) do
+ process_cookie(val, result)
+ end
+ return result
+end
+
local function process_headers(headers)
for header, value in pairs(headers) do
if type(value) == 'table' then
@@ -185,6 +298,9 @@ curl_mt = {
end
local resp = self.curl:request(method, url, body, opts or {})
if resp and resp.headers then
+ if resp.headers['set-cookie'] ~= nil then
+ resp.cookies = process_cookies(resp.headers['set-cookie'])
+ end
resp.headers = process_headers(resp.headers)
end
return resp
diff --git a/test/app-tap/http_client.test.lua b/test/app-tap/http_client.test.lua
index 54f61be..887395d 100755
--- a/test/app-tap/http_client.test.lua
+++ b/test/app-tap/http_client.test.lua
@@ -197,13 +197,24 @@ local function test_errors(test)
end
local function test_headers(test, url, opts)
- test:plan(4)
+ test:plan(15)
local http = client:new()
local r = http:get(url .. 'headers', opts)
test:is(type(r.headers["set-cookie"]), 'string', "set-cookie check")
- test:is(r.headers["set-cookie"], "likes=cheese,age=17", "set-cookie check")
+ test:ok(r.headers["set-cookie"]:match("likes=cheese"), "set-cookie check")
+ test:ok(r.headers["set-cookie"]:match("age = 17"), "set-cookie check")
test:is(r.headers["content-type"], "application/json", "content-type check")
test:is(r.headers["my_header"], "value1,value2", "other header check")
+ test:is(r.cookies["likes"][1], "cheese", "cookie value check")
+ test:ok(r.cookies["likes"][2][1]:match("Expires"), "cookie option check")
+ test:ok(r.cookies["likes"][2][3]:match("HttpOnly"), "cookie option check")
+ test:is(r.cookies["age"][1], "17", "cookie value check")
+ test:is(#r.cookies["age"][2], 1, "cookie option check")
+ test:is(r.cookies["age"][2][1], "Secure", "cookie option check")
+ test:ok(r.cookies["good_name"] ~= nil , "cookie name check")
+ test:ok(r.cookies["bad at name"] == nil , "cookie name check")
+ test:ok(r.cookies["badname"] == nil , "cookie name check")
+ test:ok(r.cookies["badcookie"] == nil , "cookie name check")
end
local function test_special_methods(test, url, opts)
diff --git a/test/app-tap/httpd.py b/test/app-tap/httpd.py
index 03ebb97..9a09637 100755
--- a/test/app-tap/httpd.py
+++ b/test/app-tap/httpd.py
@@ -26,8 +26,11 @@ def headers():
body = [b"cookies"]
headers = [('Content-Type', 'application/json'),
('Content-Type', 'application/yaml'),
- ('Set-Cookie', 'likes=cheese'),
- ('Set-Cookie', 'age=17'),
+ ('Set-Cookie', 'likes=cheese; Expires=Wed, 21 Oct 2015 07:28:00 GMT; Secure; HttpOnly'),
+ ('Set-Cookie', 'bad at name=no;'),
+ ('Set-Cookie', 'badcookie'),
+ ('Set-Cookie', 'good_name=yes;'),
+ ('Set-Cookie', 'age = 17; NOSuchOption; EmptyOption=Value;Secure'),
('my_header', 'value1'),
('my_header', 'value2'),
]
--
2.7.4
More information about the Tarantool-patches
mailing list