Permalink
Cannot retrieve contributors at this time
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
lua-http/http/headers.lua
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
237 lines (206 sloc)
4.88 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| --[[ | |
| HTTP Header data structure/type | |
| Design criteria: | |
| - the same header field is allowed more than once | |
| - must be able to fetch separate occurences (important for some headers e.g. Set-Cookie) | |
| - optionally available as comma separated list | |
| - http2 adds flag to headers that they should never be indexed | |
| - header order should be recoverable | |
| I chose to implement headers as an array of entries. | |
| An index of field name => array indices is kept. | |
| ]] | |
| local unpack = table.unpack or unpack -- luacheck: ignore 113 143 | |
| local entry_methods = {} | |
| local entry_mt = { | |
| __name = "http.headers.entry"; | |
| __index = entry_methods; | |
| } | |
| local never_index_defaults = { | |
| authorization = true; | |
| ["proxy-authorization"] = true; | |
| cookie = true; | |
| ["set-cookie"] = true; | |
| } | |
| local function new_entry(name, value, never_index) | |
| if never_index == nil then | |
| never_index = never_index_defaults[name] or false | |
| end | |
| return setmetatable({ | |
| name = name; | |
| value = value; | |
| never_index = never_index; | |
| }, entry_mt) | |
| end | |
| function entry_methods:modify(value, never_index) | |
| self.value = value | |
| if never_index == nil then | |
| never_index = never_index_defaults[self.name] or false | |
| end | |
| self.never_index = never_index | |
| end | |
| function entry_methods:unpack() | |
| return self.name, self.value, self.never_index | |
| end | |
| function entry_methods:clone() | |
| return new_entry(self.name, self.value, self.never_index) | |
| end | |
| local headers_methods = {} | |
| local headers_mt = { | |
| __name = "http.headers"; | |
| __index = headers_methods; | |
| } | |
| local function new_headers() | |
| return setmetatable({ | |
| _n = 0; | |
| _data = {}; | |
| _index = {}; | |
| }, headers_mt) | |
| end | |
| function headers_methods:len() | |
| return self._n | |
| end | |
| headers_mt.__len = headers_methods.len | |
| function headers_mt:__tostring() | |
| return string.format("http.headers{%d headers}", self._n) | |
| end | |
| local function add_to_index(_index, name, i) | |
| local dex = _index[name] | |
| if dex == nil then | |
| dex = {n=1, i} | |
| _index[name] = dex | |
| else | |
| local n = dex.n + 1 | |
| dex[n] = i | |
| dex.n = n | |
| end | |
| end | |
| local function rebuild_index(self) | |
| local index = {} | |
| for i=1, self._n do | |
| local entry = self._data[i] | |
| add_to_index(index, entry.name, i) | |
| end | |
| self._index = index | |
| end | |
| function headers_methods:clone() | |
| local index, new_data = {}, {} | |
| for i=1, self._n do | |
| local entry = self._data[i] | |
| new_data[i] = entry:clone() | |
| add_to_index(index, entry.name, i) | |
| end | |
| return setmetatable({ | |
| _n = self._n; | |
| _data = new_data; | |
| _index = index; | |
| }, headers_mt) | |
| end | |
| function headers_methods:append(name, ...) | |
| local n = self._n + 1 | |
| self._data[n] = new_entry(name, ...) | |
| add_to_index(self._index, name, n) | |
| self._n = n | |
| end | |
| function headers_methods:each() | |
| local i = 0 | |
| return function(self) -- luacheck: ignore 432 | |
| if i >= self._n then return end | |
| i = i + 1 | |
| local entry = self._data[i] | |
| return entry:unpack() | |
| end, self | |
| end | |
| headers_mt.__pairs = headers_methods.each | |
| function headers_methods:has(name) | |
| local dex = self._index[name] | |
| return dex ~= nil | |
| end | |
| function headers_methods:delete(name) | |
| local dex = self._index[name] | |
| if dex then | |
| local n = dex.n | |
| for i=n, 1, -1 do | |
| table.remove(self._data, dex[i]) | |
| end | |
| self._n = self._n - n | |
| rebuild_index(self) | |
| return true | |
| else | |
| return false | |
| end | |
| end | |
| function headers_methods:geti(i) | |
| local e = self._data[i] | |
| if e == nil then return nil end | |
| return e:unpack() | |
| end | |
| function headers_methods:get_as_sequence(name) | |
| local dex = self._index[name] | |
| if dex == nil then return { n = 0; } end | |
| local r = { n = dex.n; } | |
| for i=1, r.n do | |
| r[i] = self._data[dex[i]].value | |
| end | |
| return r | |
| end | |
| function headers_methods:get(name) | |
| local r = self:get_as_sequence(name) | |
| return unpack(r, 1, r.n) | |
| end | |
| function headers_methods:get_comma_separated(name) | |
| local r = self:get_as_sequence(name) | |
| if r.n == 0 then | |
| return nil | |
| else | |
| return table.concat(r, ",", 1, r.n) | |
| end | |
| end | |
| function headers_methods:modifyi(i, ...) | |
| local e = self._data[i] | |
| if e == nil then error("invalid index") end | |
| e:modify(...) | |
| end | |
| function headers_methods:upsert(name, ...) | |
| local dex = self._index[name] | |
| if dex == nil then | |
| self:append(name, ...) | |
| else | |
| assert(dex[2] == nil, "Cannot upsert multi-valued field") | |
| self:modifyi(dex[1], ...) | |
| end | |
| end | |
| local function default_cmp(a, b) | |
| if a.name ~= b.name then | |
| -- Things with a colon *must* be before others | |
| local a_is_colon = a.name:sub(1,1) == ":" | |
| local b_is_colon = b.name:sub(1,1) == ":" | |
| if a_is_colon and not b_is_colon then | |
| return true | |
| elseif not a_is_colon and b_is_colon then | |
| return false | |
| else | |
| return a.name < b.name | |
| end | |
| end | |
| if a.value ~= b.value then | |
| return a.value < b.value | |
| end | |
| return a.never_index | |
| end | |
| function headers_methods:sort() | |
| table.sort(self._data, default_cmp) | |
| rebuild_index(self) | |
| end | |
| function headers_methods:dump(file, prefix) | |
| file = file or io.stderr | |
| prefix = prefix or "" | |
| for name, value in self:each() do | |
| assert(file:write(string.format("%s%s: %s\n", prefix, name, value))) | |
| end | |
| assert(file:flush()) | |
| end | |
| return { | |
| new = new_headers; | |
| methods = headers_methods; | |
| mt = headers_mt; | |
| } |