[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