├── .gitignore ├── Makefile ├── README.md ├── lib └── resty │ └── cookie.lua ├── rockspecs ├── lua-resty-cookie-0.1.0-2.rockspec └── lua-resty-cookie-scm-1.rockspec └── t └── sanity.t /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.swo 3 | *~ 4 | go 5 | t/servroot/ 6 | reindex 7 | *.t_ 8 | tags 9 | luacov.report.out 10 | luacov.stats.out 11 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | OPENRESTY_PREFIX=/usr/local/openresty 2 | 3 | PREFIX ?= /usr/local 4 | LUA_INCLUDE_DIR ?= $(PREFIX)/include 5 | LUA_LIB_DIR ?= $(PREFIX)/lib/lua/$(LUA_VERSION) 6 | INSTALL ?= install 7 | 8 | .PHONY: all test install 9 | 10 | all: ; 11 | 12 | install: all 13 | $(INSTALL) -d $(DESTDIR)/$(LUA_LIB_DIR)/resty/ 14 | $(INSTALL) lib/resty/*.lua $(DESTDIR)/$(LUA_LIB_DIR)/resty/ 15 | 16 | test: all 17 | PATH=$(OPENRESTY_PREFIX)/nginx/sbin:$$PATH prove -I../test-nginx/lib -r t 18 | 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Name 2 | ==== 3 | 4 | lua-resty-cookie - This library parses HTTP Cookie header for Nginx and returns each field in the cookie. 5 | 6 | Table of Contents 7 | ================= 8 | 9 | * [Name](#name) 10 | * [Status](#status) 11 | * [Synopsis](#synopsis) 12 | * [Methods](#methods) 13 | * [new](#new) 14 | * [get](#get) 15 | * [get_all](#get_all) 16 | * [get_cookie_size](#get_cookie_size) 17 | * [set](#set) 18 | * [get_cookie_string](#get_cookie_string) 19 | * [Installation](#installation) 20 | * [Authors](#authors) 21 | * [Copyright and License](#copyright-and-license) 22 | 23 | Status 24 | ====== 25 | 26 | This library is production ready. 27 | 28 | Synopsis 29 | ======== 30 | ```lua 31 | lua_package_path "/path/to/lua-resty-cookie/lib/?.lua;;"; 32 | 33 | server { 34 | location /test { 35 | content_by_lua ' 36 | local ck = require "resty.cookie" 37 | local cookie, err = ck:new() 38 | if not cookie then 39 | ngx.log(ngx.ERR, err) 40 | return 41 | end 42 | 43 | -- get single cookie 44 | local field, err = cookie:get("lang") 45 | if not field then 46 | ngx.log(ngx.ERR, err) 47 | return 48 | end 49 | ngx.say("lang", " => ", field) 50 | 51 | -- get all cookies 52 | local fields, err = cookie:get_all() 53 | if not fields then 54 | ngx.log(ngx.ERR, err) 55 | return 56 | end 57 | 58 | for k, v in pairs(fields) do 59 | ngx.say(k, " => ", v) 60 | end 61 | 62 | -- set one cookie 63 | local ok, err = cookie:set({ 64 | key = "Name", value = "Bob", path = "/", 65 | domain = "example.com", secure = true, httponly = true, 66 | expires = "Wed, 09 Jun 2021 10:18:14 GMT", max_age = 50, 67 | samesite = "Strict", extension = "a4334aebaec" 68 | }) 69 | if not ok then 70 | ngx.log(ngx.ERR, err) 71 | return 72 | end 73 | 74 | -- set another cookie, both cookies will appear in HTTP response 75 | local ok, err = cookie:set({ 76 | key = "Age", value = "20", 77 | }) 78 | if not ok then 79 | ngx.log(ngx.ERR, err) 80 | return 81 | end 82 | '; 83 | } 84 | } 85 | ``` 86 | 87 | Methods 88 | ======= 89 | 90 | [Back to TOC](#table-of-contents) 91 | 92 | new 93 | --- 94 | `syntax: cookie_obj = cookie()` 95 | 96 | Create a new cookie object for current request. You can get parsed cookie from client or set cookie to client later using this object. 97 | 98 | [Back to TOC](#table-of-contents) 99 | 100 | get 101 | --- 102 | `syntax: cookie_val, err = cookie_obj:get(cookie_name)` 103 | 104 | Get a single client cookie value. On error, returns `nil` and an error message. 105 | 106 | [Back to TOC](#table-of-contents) 107 | 108 | get_all 109 | ------- 110 | `syntax: fields, err = cookie_obj:get_all()` 111 | 112 | Get all client cookie key/value pairs in a lua table. On error, returns `nil` and an error message. 113 | 114 | [Back to TOC](#table-of-contents) 115 | 116 | get_cookie_size 117 | ------- 118 | `syntax: size = cookie_obj:get_cookie_size()` 119 | 120 | Get the cookie size, i.e the string length of the cookie header value. 121 | 122 | [Back to TOC](#table-of-contents) 123 | 124 | set 125 | --- 126 | ```lua 127 | syntax: ok, err = cookie_obj:set({ 128 | key = "Name", 129 | value = "Bob", 130 | path = "/", 131 | domain = "example.com", 132 | secure = true, httponly = true, 133 | expires = "Wed, 09 Jun 2021 10:18:14 GMT", 134 | max_age = 50, 135 | samesite = "Strict", 136 | extension = "a4334aebaec" 137 | }) 138 | ``` 139 | 140 | Set a cookie to client. This will add a new 'Set-Cookie' response header. `key` and `value` are required, all other fields are optional. 141 | If the same cookie (whole cookie string, e.g. "Name=Bob; Expires=Wed, 09 Jun 2021 10:18:14 GMT; Max-Age=50; Domain=example.com; Path=/; Secure; HttpOnly;") has already been setted, new cookie will be ignored. 142 | 143 | [Back to TOC](#table-of-contents) 144 | 145 | get_cookie_string 146 | --- 147 | ```lua 148 | syntax: cookie_string, err = cookie.get_cookie_string({ --[[ see "set" method ]] }) 149 | ``` 150 | Returns a cookie string representing the table passed. See the `set` method for details, but unlike `set`, this function doesn't change the 151 | current request response, but just return the generated string. On error, returns `nil` and an error message. 152 | 153 | This is a static function, not a method of the `cookie` object. 154 | 155 | [Back to TOC](#table-of-contents) 156 | 157 | Installation 158 | ============ 159 | 160 | You need to compile [ngx_lua](https://github.com/chaoslawful/lua-nginx-module/tags) with your Nginx. 161 | 162 | You need to configure 163 | the [lua_package_path](https://github.com/chaoslawful/lua-nginx-module#lua_package_path) directive to 164 | add the path of your `lua-resty-cookie` source tree to ngx_lua's Lua module search path, as in 165 | 166 | # nginx.conf 167 | http { 168 | lua_package_path "/path/to/lua-resty-cookie/lib/?.lua;;"; 169 | ... 170 | } 171 | 172 | and then load the library in Lua: 173 | 174 | local ck = require "resty.cookie" 175 | 176 | [Back to TOC](#table-of-contents) 177 | 178 | Authors 179 | ======= 180 | 181 | Jiale Zhi , CloudFlare Inc. 182 | 183 | Yichun Zhang (agentzh) , CloudFlare Inc. 184 | 185 | [Back to TOC](#table-of-contents) 186 | 187 | Copyright and License 188 | ===================== 189 | 190 | This module is licensed under the BSD license. 191 | 192 | Copyright (C) 2013, by Jiale Zhi , CloudFlare Inc. 193 | 194 | Copyright (C) 2013, by Yichun Zhang , CloudFlare Inc. 195 | 196 | All rights reserved. 197 | 198 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 199 | 200 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 201 | 202 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 203 | 204 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 205 | 206 | [Back to TOC](#table-of-contents) 207 | 208 | -------------------------------------------------------------------------------- /lib/resty/cookie.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (C) 2013-2016 Jiale Zhi (calio), CloudFlare Inc. 2 | -- See RFC6265 http://tools.ietf.org/search/rfc6265 3 | -- require "luacov" 4 | 5 | local type = type 6 | local byte = string.byte 7 | local sub = string.sub 8 | local format = string.format 9 | local log = ngx.log 10 | local ERR = ngx.ERR 11 | local WARN = ngx.WARN 12 | local ngx_header = ngx.header 13 | 14 | local EQUAL = byte("=") 15 | local SEMICOLON = byte(";") 16 | local SPACE = byte(" ") 17 | local HTAB = byte("\t") 18 | 19 | -- table.new(narr, nrec) 20 | local ok, new_tab = pcall(require, "table.new") 21 | if not ok then 22 | new_tab = function () return {} end 23 | end 24 | 25 | local ok, clear_tab = pcall(require, "table.clear") 26 | if not ok then 27 | clear_tab = function(tab) for k, _ in pairs(tab) do tab[k] = nil end end 28 | end 29 | 30 | local _M = new_tab(0, 2) 31 | 32 | _M._VERSION = '0.01' 33 | 34 | 35 | local function get_cookie_table(text_cookie) 36 | if type(text_cookie) ~= "string" then 37 | log(ERR, format("expect text_cookie to be \"string\" but found %s", 38 | type(text_cookie))) 39 | return {} 40 | end 41 | 42 | local EXPECT_KEY = 1 43 | local EXPECT_VALUE = 2 44 | local EXPECT_SP = 3 45 | 46 | local n = 0 47 | local len = #text_cookie 48 | 49 | for i=1, len do 50 | if byte(text_cookie, i) == SEMICOLON then 51 | n = n + 1 52 | end 53 | end 54 | 55 | local cookie_table = new_tab(0, n + 1) 56 | 57 | local state = EXPECT_SP 58 | local i = 1 59 | local j = 1 60 | local key, value 61 | 62 | while j <= len do 63 | if state == EXPECT_KEY then 64 | if byte(text_cookie, j) == EQUAL then 65 | key = sub(text_cookie, i, j - 1) 66 | state = EXPECT_VALUE 67 | i = j + 1 68 | end 69 | elseif state == EXPECT_VALUE then 70 | if byte(text_cookie, j) == SEMICOLON 71 | or byte(text_cookie, j) == SPACE 72 | or byte(text_cookie, j) == HTAB 73 | then 74 | value = sub(text_cookie, i, j - 1) 75 | cookie_table[key] = value 76 | 77 | key, value = nil, nil 78 | state = EXPECT_SP 79 | i = j + 1 80 | end 81 | elseif state == EXPECT_SP then 82 | if byte(text_cookie, j) ~= SPACE 83 | and byte(text_cookie, j) ~= HTAB 84 | then 85 | state = EXPECT_KEY 86 | i = j 87 | j = j - 1 88 | end 89 | end 90 | j = j + 1 91 | end 92 | 93 | if key ~= nil and value == nil then 94 | cookie_table[key] = sub(text_cookie, i) 95 | end 96 | 97 | return cookie_table 98 | end 99 | 100 | function _M.new(self) 101 | local _cookie = ngx.var.http_cookie 102 | --if not _cookie then 103 | --return nil, "no cookie found in current request" 104 | --end 105 | return setmetatable({ _cookie = _cookie, set_cookie_table = new_tab(4, 0) }, 106 | { __index = self }) 107 | end 108 | 109 | function _M.get(self, key) 110 | if not self._cookie then 111 | return nil, "no cookie found in the current request" 112 | end 113 | if self.cookie_table == nil then 114 | self.cookie_table = get_cookie_table(self._cookie) 115 | end 116 | 117 | return self.cookie_table[key] 118 | end 119 | 120 | function _M.get_all(self) 121 | if not self._cookie then 122 | return nil, "no cookie found in the current request" 123 | end 124 | 125 | if self.cookie_table == nil then 126 | self.cookie_table = get_cookie_table(self._cookie) 127 | end 128 | 129 | return self.cookie_table 130 | end 131 | 132 | function _M.get_cookie_size(self) 133 | if not self._cookie then 134 | return 0 135 | end 136 | 137 | return string.len(self._cookie) 138 | end 139 | 140 | local function bake(cookie) 141 | if not cookie.key or not cookie.value then 142 | return nil, 'missing cookie field "key" or "value"' 143 | end 144 | 145 | if cookie["max-age"] then 146 | cookie.max_age = cookie["max-age"] 147 | end 148 | 149 | if (cookie.samesite) then 150 | local samesite = cookie.samesite 151 | 152 | -- if we don't have a valid-looking attribute, ignore the attribute 153 | if (samesite ~= "Strict" and samesite ~= "Lax" and samesite ~= "None") then 154 | log(WARN, "SameSite value must be 'Strict', 'Lax' or 'None'") 155 | cookie.samesite = nil 156 | end 157 | end 158 | 159 | local str = cookie.key .. "=" .. cookie.value 160 | .. (cookie.expires and "; Expires=" .. cookie.expires or "") 161 | .. (cookie.max_age and "; Max-Age=" .. cookie.max_age or "") 162 | .. (cookie.domain and "; Domain=" .. cookie.domain or "") 163 | .. (cookie.path and "; Path=" .. cookie.path or "") 164 | .. (cookie.secure and "; Secure" or "") 165 | .. (cookie.httponly and "; HttpOnly" or "") 166 | .. (cookie.samesite and "; SameSite=" .. cookie.samesite or "") 167 | .. (cookie.extension and "; " .. cookie.extension or "") 168 | return str 169 | end 170 | 171 | function _M.set(self, cookie) 172 | local cookie_str, err = bake(cookie) 173 | if not cookie_str then 174 | return nil, err 175 | end 176 | 177 | local set_cookie = ngx_header['Set-Cookie'] 178 | local set_cookie_type = type(set_cookie) 179 | local t = self.set_cookie_table 180 | clear_tab(t) 181 | 182 | if set_cookie_type == "string" then 183 | -- only one cookie has been setted 184 | if set_cookie ~= cookie_str then 185 | t[1] = set_cookie 186 | t[2] = cookie_str 187 | ngx_header['Set-Cookie'] = t 188 | end 189 | elseif set_cookie_type == "table" then 190 | -- more than one cookies has been setted 191 | local size = #set_cookie 192 | 193 | -- we can not set cookie like ngx.header['Set-Cookie'][3] = val 194 | -- so create a new table, copy all the values, and then set it back 195 | for i=1, size do 196 | t[i] = ngx_header['Set-Cookie'][i] 197 | if t[i] == cookie_str then 198 | -- new cookie is duplicated 199 | return true 200 | end 201 | end 202 | t[size + 1] = cookie_str 203 | ngx_header['Set-Cookie'] = t 204 | else 205 | -- no cookie has been setted 206 | ngx_header['Set-Cookie'] = cookie_str 207 | end 208 | return true 209 | end 210 | 211 | _M.get_cookie_string = bake 212 | 213 | return _M 214 | -------------------------------------------------------------------------------- /rockspecs/lua-resty-cookie-0.1.0-2.rockspec: -------------------------------------------------------------------------------- 1 | package = "lua-resty-cookie" 2 | version = "0.1.0-2" 3 | 4 | source = { 5 | url = "https://github.com/cloudflare/lua-resty-cookie.git", 6 | tag = "v0.1.0", 7 | } 8 | 9 | description = { 10 | summary = "Lua library for HTTP cookie manipulations for OpenResty/ngx_lua", 11 | homepage = "https://github.com/cloudflare/lua-resty-cookie", 12 | license = "BSD", 13 | } 14 | 15 | dependencies = { 16 | "lua >= 5.1", -- lua-nginx-module needed 17 | } 18 | 19 | build = { 20 | type = "builtin", 21 | modules = { 22 | ["resty.cookie"] = "lib/resty/cookie.lua" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /rockspecs/lua-resty-cookie-scm-1.rockspec: -------------------------------------------------------------------------------- 1 | package = "lua-resty-cookie" 2 | version = "scm-1" 3 | 4 | source = { 5 | url = "https://github.com/cloudflare/lua-resty-cookie.git", 6 | } 7 | 8 | description = { 9 | summary = "Lua library for HTTP cookie manipulations for OpenResty/ngx_lua", 10 | homepage = "https://github.com/cloudflare/lua-resty-cookie", 11 | license = "BSD", 12 | } 13 | 14 | dependencies = { 15 | "lua >= 5.1", -- lua-nginx-module needed 16 | } 17 | 18 | build = { 19 | type = "builtin", 20 | modules = { 21 | ["resty.cookie"] = "lib/resty/cookie.lua" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /t/sanity.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et: 2 | 3 | use Test::Nginx::Socket; 4 | use Cwd qw(cwd); 5 | 6 | repeat_each(2); 7 | 8 | plan tests => repeat_each() * (blocks() * 3 + 7); 9 | 10 | my $pwd = cwd(); 11 | 12 | our $HttpConfig = qq{ 13 | lua_package_path "$pwd/lib/?.lua;;"; 14 | lua_package_cpath "/usr/local/openresty-debug/lualib/?.so;/usr/local/openresty/lualib/?.so;;"; 15 | }; 16 | 17 | $ENV{TEST_NGINX_RESOLVER} = '8.8.8.8'; 18 | 19 | #no_long_string(); 20 | 21 | log_level('debug'); 22 | 23 | run_tests(); 24 | 25 | __DATA__ 26 | 27 | === TEST 1: sanity 28 | --- http_config eval: $::HttpConfig 29 | --- config 30 | location /t { 31 | content_by_lua ' 32 | local ck = require "resty.cookie" 33 | local cookie, err = ck:new() 34 | if not cookie then 35 | ngx.log(ngx.ERR, err) 36 | return 37 | end 38 | 39 | local fields = cookie:get_all() 40 | 41 | local keys = {} 42 | for key in pairs(fields) do 43 | table.insert(keys, key) 44 | end 45 | table.sort(keys) 46 | 47 | for _, key in ipairs(keys) do 48 | ngx.say(key, " => ", fields[key]) 49 | end 50 | '; 51 | } 52 | --- request 53 | GET /t 54 | --- more_headers 55 | Cookie: SID=31d4d96e407aad42; lang=en-US 56 | --- no_error_log 57 | [error] 58 | --- response_body 59 | SID => 31d4d96e407aad42 60 | lang => en-US 61 | 62 | === TEST 2: sanity 2 63 | --- http_config eval: $::HttpConfig 64 | --- config 65 | location /t { 66 | content_by_lua ' 67 | local ck = require "resty.cookie" 68 | local cookie, err = ck:new() 69 | if not cookie then 70 | ngx.log(ngx.ERR, err) 71 | return 72 | end 73 | 74 | local field = cookie:get("lang") 75 | ngx.say("lang", " => ", field) 76 | '; 77 | } 78 | --- request 79 | GET /t 80 | --- more_headers 81 | Cookie: SID=31d4d96e407aad42; lang=en-US 82 | --- no_error_log 83 | [error] 84 | --- response_body 85 | lang => en-US 86 | 87 | 88 | 89 | === TEST 3: no cookie header 90 | --- http_config eval: $::HttpConfig 91 | --- config 92 | location /t { 93 | content_by_lua ' 94 | local ck = require "resty.cookie" 95 | local cookie, err = ck:new() 96 | if not cookie then 97 | ngx.log(ngx.ERR, err) 98 | ngx.say(err) 99 | return 100 | end 101 | 102 | local field, err = cookie:get("lang") 103 | if not field then 104 | ngx.log(ngx.ERR, err) 105 | ngx.say(err) 106 | return 107 | end 108 | ngx.say("lang", " => ", field) 109 | '; 110 | } 111 | --- request 112 | GET /t 113 | --- error_log 114 | no cookie found in the current request 115 | --- response_body 116 | no cookie found in the current request 117 | 118 | 119 | 120 | === TEST 4: empty value 121 | --- http_config eval: $::HttpConfig 122 | --- config 123 | location /t { 124 | content_by_lua ' 125 | local ck = require "resty.cookie" 126 | local cookie, err = ck:new() 127 | if not cookie then 128 | ngx.log(ngx.ERR, err) 129 | return 130 | end 131 | 132 | local fields = cookie:get_all() 133 | 134 | for k, v in pairs(fields) do 135 | ngx.say(k, " => ", v) 136 | end 137 | '; 138 | } 139 | --- request 140 | GET /t 141 | --- more_headers 142 | Cookie: SID= 143 | --- no_error_log 144 | [error] 145 | --- response_body 146 | SID => 147 | 148 | 149 | 150 | === TEST 5: cookie with space/tab 151 | --- http_config eval: $::HttpConfig 152 | --- config 153 | location /t { 154 | content_by_lua ' 155 | local ck = require "resty.cookie" 156 | local cookie, err = ck:new() 157 | if not cookie then 158 | ngx.log(ngx.ERR, err) 159 | return 160 | end 161 | 162 | local fields = cookie:get_all() 163 | 164 | for k, v in pairs(fields) do 165 | ngx.say(k, " => ", v) 166 | end 167 | '; 168 | } 169 | --- request 170 | GET /t 171 | --- more_headers eval: "Cookie: SID=foo\t" 172 | --- no_error_log 173 | [error] 174 | --- response_body 175 | SID => foo 176 | 177 | 178 | 179 | === TEST 6: set cookie 180 | --- http_config eval: $::HttpConfig 181 | --- config 182 | location /t { 183 | content_by_lua ' 184 | local ck = require "resty.cookie" 185 | local cookie, err = ck:new() 186 | if not cookie then 187 | ngx.log(ngx.ERR, err) 188 | return 189 | end 190 | 191 | local ok, err = cookie:set({ 192 | key = "Name", value = "Bob", path = "/", 193 | domain = "example.com", secure = true, httponly = true, 194 | expires = "Wed, 09 Jun 2021 10:18:14 GMT", max_age = 50, 195 | samesite = "Strict", extension = "a4334aebaec" 196 | }) 197 | if not ok then 198 | ngx.log(ngx.ERR, err) 199 | return 200 | end 201 | ngx.say("Set cookie") 202 | '; 203 | } 204 | --- request 205 | GET /t 206 | --- no_error_log 207 | [error] 208 | --- response_headers 209 | Set-Cookie: Name=Bob; Expires=Wed, 09 Jun 2021 10:18:14 GMT; Max-Age=50; Domain=example.com; Path=/; Secure; HttpOnly; SameSite=Strict; a4334aebaec 210 | --- response_body 211 | Set cookie 212 | 213 | 214 | 215 | === TEST 7: set multiple cookie 216 | --- http_config eval: $::HttpConfig 217 | --- config 218 | location /t { 219 | content_by_lua ' 220 | local ck = require "resty.cookie" 221 | local cookie, err = ck:new() 222 | if not cookie then 223 | ngx.log(ngx.ERR, err) 224 | return 225 | end 226 | 227 | local ok, err = cookie:set({ 228 | key = "Name", value = "Bob", path = "/", 229 | }) 230 | if not ok then 231 | ngx.log(ngx.ERR, err) 232 | return 233 | end 234 | 235 | local ok, err = cookie:set({ 236 | key = "Age", value = "20", 237 | }) 238 | if not ok then 239 | ngx.log(ngx.ERR, err) 240 | return 241 | end 242 | 243 | local ok, err = cookie:set({ 244 | key = "ID", value = "0xf7898", 245 | expires = "Wed, 09 Jun 2021 10:18:14 GMT" 246 | }) 247 | if not ok then 248 | ngx.log(ngx.ERR, err) 249 | return 250 | end 251 | ngx.say("Set cookie") 252 | '; 253 | } 254 | --- request 255 | GET /t 256 | --- no_error_log 257 | [error] 258 | --- comment 259 | because "--- response_headers" does not work with multiple headers with the same 260 | key, so use "--- raw_response_headers_like" instead 261 | --- raw_response_headers_like: Set-Cookie: Name=Bob; Path=/\r\nSet-Cookie: Age=20\r\nSet-Cookie: ID=0xf7898; Expires=Wed, 09 Jun 2021 10:18:14 GMT 262 | --- response_body 263 | Set cookie 264 | 265 | 266 | 267 | === TEST 8: remove duplicated cookies in cookie:set 268 | --- http_config eval: $::HttpConfig 269 | --- config 270 | location /t { 271 | content_by_lua ' 272 | local ck = require "resty.cookie" 273 | local cookie, err = ck:new() 274 | if not cookie then 275 | ngx.log(ngx.ERR, err) 276 | return 277 | end 278 | 279 | local ok, err = cookie:set({ 280 | key = "Name", value = "Bob", path = "/", 281 | }) 282 | if not ok then 283 | ngx.log(ngx.ERR, err) 284 | return 285 | end 286 | 287 | local ok, err = cookie:set({ 288 | key = "Age", value = "20", 289 | }) 290 | if not ok then 291 | ngx.log(ngx.ERR, err) 292 | return 293 | end 294 | 295 | local ok, err = cookie:set({ 296 | key = "Name", value = "Bob", path = "/", 297 | }) 298 | if not ok then 299 | ngx.log(ngx.ERR, err) 300 | return 301 | end 302 | 303 | ngx.say("Set cookie") 304 | '; 305 | } 306 | --- request 307 | GET /t 308 | --- no_error_log 309 | [error] 310 | --- raw_response_headers_like: Set-Cookie: Name=Bob; Path=/\r\nSet-Cookie: Age=20\r\n 311 | --- raw_response_headers_unlike: Set-Cookie: Name=Bob; Path=/\r\nSet-Cookie: Age=20\r\nSet-Cookie: Name=Bob; Path=/ 312 | --- response_body 313 | Set cookie 314 | 315 | 316 | === TEST 9: set cookie with invalid SameSite attribute 317 | --- http_config eval: $::HttpConfig 318 | --- config 319 | location /t { 320 | content_by_lua ' 321 | local ck = require "resty.cookie" 322 | local cookie, err = ck:new() 323 | if not cookie then 324 | ngx.log(ngx.ERR, err) 325 | return 326 | end 327 | 328 | local ok, err = cookie:set({ 329 | key = "Name", value = "Bob", path = "/", 330 | domain = "example.com", secure = true, httponly = true, 331 | expires = "Wed, 09 Jun 2021 10:18:14 GMT", max_age = 50, 332 | samesite = "blahblah", extension = "a4334aebaec" 333 | }) 334 | if not ok then 335 | ngx.log(ngx.ERR, err) 336 | return 337 | end 338 | ngx.say("Set cookie") 339 | '; 340 | } 341 | --- request 342 | GET /t 343 | --- no_error_log 344 | [error] 345 | --- error_log 346 | SameSite value must be 'Strict', 'Lax' or 'None' 347 | --- response_headers 348 | Set-Cookie: Name=Bob; Expires=Wed, 09 Jun 2021 10:18:14 GMT; Max-Age=50; Domain=example.com; Path=/; Secure; HttpOnly; a4334aebaec 349 | --- response_body 350 | Set cookie 351 | 352 | 353 | === TEST 10: set cookie with None as a valid SameSite attribute 354 | --- http_config eval: $::HttpConfig 355 | --- config 356 | location /t { 357 | content_by_lua ' 358 | local ck = require "resty.cookie" 359 | local cookie, err = ck:new() 360 | if not cookie then 361 | ngx.log(ngx.ERR, err) 362 | return 363 | end 364 | 365 | local ok, err = cookie:set({ 366 | key = "Name", value = "Bob", path = "/", 367 | domain = "example.com", secure = true, httponly = true, 368 | expires = "Wed, 09 Jun 2021 10:18:14 GMT", max_age = 50, 369 | samesite = "None", extension = "a4334aebaec" 370 | }) 371 | if not ok then 372 | ngx.log(ngx.ERR, err) 373 | return 374 | end 375 | ngx.say("Set cookie") 376 | '; 377 | } 378 | --- request 379 | GET /t 380 | --- no_error_log 381 | [error] 382 | --- response_headers 383 | Set-Cookie: Name=Bob; Expires=Wed, 09 Jun 2021 10:18:14 GMT; Max-Age=50; Domain=example.com; Path=/; Secure; HttpOnly; SameSite=None; a4334aebaec 384 | --- response_body 385 | Set cookie 386 | 387 | === TEST 11: Cookie size 388 | --- http_config eval: $::HttpConfig 389 | --- config 390 | location /t { 391 | content_by_lua ' 392 | local ck = require "resty.cookie" 393 | local cookie, err = ck:new() 394 | if not cookie then 395 | ngx.log(ngx.ERR, err) 396 | return 397 | end 398 | 399 | local size = cookie:get_cookie_size() 400 | ngx.say("size", " => ", size) 401 | '; 402 | } 403 | --- request 404 | GET /t 405 | --- more_headers 406 | Cookie: SID=31d4d96e407aad42; lang=en-US 407 | --- no_error_log 408 | [error] 409 | --- response_body 410 | size => 32 411 | 412 | 413 | 414 | === TEST 12: no cookie header 415 | --- http_config eval: $::HttpConfig 416 | --- config 417 | location /t { 418 | content_by_lua ' 419 | local ck = require "resty.cookie" 420 | local cookie, err = ck:new() 421 | if not cookie then 422 | ngx.log(ngx.ERR, err) 423 | ngx.say(err) 424 | return 425 | end 426 | 427 | local size = cookie:get_cookie_size() 428 | ngx.say("size", " => ", size) 429 | '; 430 | } 431 | --- request 432 | GET /t 433 | --- no_error_log 434 | [error] 435 | --- response_body 436 | size => 0 437 | 438 | 439 | 440 | === TEST 13: get_cookie_string 441 | --- http_config eval: $::HttpConfig 442 | --- config 443 | location /t { 444 | content_by_lua ' 445 | local ck = require "resty.cookie" 446 | local cookie, err = ck.get_cookie_string({ 447 | key = "Name", value = "Bob", path = "/", 448 | domain = "example.com", secure = true, httponly = true, 449 | expires = "Wed, 09 Jun 2021 10:18:14 GMT", max_age = 50, 450 | samesite = "None", extension = "a4334aebaec" 451 | }) 452 | if not cookie then 453 | ngx.log(ngx.ERR, err) 454 | return 455 | end 456 | ngx.say("Cookie string: " .. cookie) 457 | '; 458 | } 459 | --- request 460 | GET /t 461 | --- no_error_log 462 | [error] 463 | --- response_body 464 | Cookie string: Name=Bob; Expires=Wed, 09 Jun 2021 10:18:14 GMT; Max-Age=50; Domain=example.com; Path=/; Secure; HttpOnly; SameSite=None; a4334aebaec 465 | --------------------------------------------------------------------------------