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?
wsapi/src/wsapi/request.lua
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
252 lines (227 sloc)
7.04 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
| local util = require"wsapi.util" | |
| local _M = {} | |
| local function split_filename(path) | |
| local name_patt = "[/\\]?([^/\\]+)$" | |
| return (string.match(path, name_patt)) | |
| end | |
| local function insert_field (tab, name, value, overwrite) | |
| if overwrite or not tab[name] then | |
| tab[name] = value | |
| else | |
| local t = type (tab[name]) | |
| if t == "table" then | |
| table.insert (tab[name], value) | |
| else | |
| tab[name] = { tab[name], value } | |
| end | |
| end | |
| end | |
| local function parse_qs(qs, tab, overwrite) | |
| tab = tab or {} | |
| if type(qs) == "string" then | |
| local url_decode = util.url_decode | |
| for key, val in string.gmatch(qs, "([^&=]+)=([^&=]*)&?") do | |
| insert_field(tab, url_decode(key), url_decode(val), overwrite) | |
| end | |
| elseif qs then | |
| error("WSAPI Request error: invalid query string") | |
| end | |
| return tab | |
| end | |
| local function get_boundary(content_type) | |
| local boundary = string.match(content_type, "boundary%=(.-)$") | |
| return "--" .. tostring(boundary) | |
| end | |
| local function break_headers(header_data) | |
| local headers = {} | |
| for type, val in string.gmatch(header_data, '([^%c%s:]+):%s+([^\n]+)') do | |
| type = string.lower(type) | |
| headers[type] = val | |
| end | |
| return headers | |
| end | |
| local function read_field_headers(input, pos) | |
| local EOH = "\r\n\r\n" | |
| local s, e = string.find(input, EOH, pos, true) | |
| if s then | |
| return break_headers(string.sub(input, pos, s-1)), e+1 | |
| else return nil, pos end | |
| end | |
| local function get_field_names(headers) | |
| local disp_header = headers["content-disposition"] or "" | |
| local attrs = {} | |
| for attr, val in string.gmatch(disp_header, ';%s*([^%s=]+)="(.-)"') do | |
| attrs[attr] = val | |
| end | |
| return attrs.name, attrs.filename and split_filename(attrs.filename) | |
| end | |
| local function read_field_contents(input, boundary, pos) | |
| local boundaryline = "\r\n" .. boundary | |
| local s, e = string.find(input, boundaryline, pos, true) | |
| if s then | |
| return string.sub(input, pos, s-1), s-pos, e+1 | |
| else return nil, 0, pos end | |
| end | |
| local function file_value(file_contents, file_name, file_size, headers) | |
| local value = { contents = file_contents, name = file_name, | |
| size = file_size } | |
| for h, v in pairs(headers) do | |
| if h ~= "content-disposition" then | |
| value[h] = v | |
| end | |
| end | |
| return value | |
| end | |
| local function fields(input, boundary) | |
| local state, _ = { } | |
| _, state.pos = string.find(input, boundary, 1, true) | |
| state.pos = state.pos + 1 | |
| return function (state, _) | |
| local headers, name, file_name, value, size | |
| headers, state.pos = read_field_headers(input, state.pos) | |
| if headers then | |
| name, file_name = get_field_names(headers) | |
| if file_name then | |
| value, size, state.pos = read_field_contents(input, boundary, | |
| state.pos) | |
| value = file_value(value, file_name, size, headers) | |
| else | |
| value, size, state.pos = read_field_contents(input, boundary, | |
| state.pos) | |
| end | |
| end | |
| return name, value | |
| end, state | |
| end | |
| local function parse_multipart_data(input, input_type, tab, overwrite) | |
| tab = tab or {} | |
| local boundary = get_boundary(input_type) | |
| for name, value in fields(input, boundary) do | |
| insert_field(tab, name, value, overwrite) | |
| end | |
| return tab | |
| end | |
| local function parse_post_data(wsapi_env, tab, overwrite) | |
| tab = tab or {} | |
| local input_type = wsapi_env.CONTENT_TYPE | |
| if string.find(input_type, "x-www-form-urlencoded", 1, true) then | |
| local length = tonumber(wsapi_env.CONTENT_LENGTH) or 0 | |
| parse_qs(wsapi_env.input:read(length) or "", tab, overwrite) | |
| elseif string.find(input_type, "multipart/form-data", 1, true) then | |
| local length = tonumber(wsapi_env.CONTENT_LENGTH) or 0 | |
| if length > 0 then | |
| parse_multipart_data(wsapi_env.input:read(length) or "", input_type, tab, overwrite) | |
| end | |
| else | |
| local length = tonumber(wsapi_env.CONTENT_LENGTH) or 0 | |
| tab.post_data = wsapi_env.input:read(length) or "" | |
| end | |
| return tab | |
| end | |
| _M.methods = {} | |
| local methods = _M.methods | |
| function methods.__index(tab, name) | |
| local func | |
| if methods[name] then | |
| func = methods[name] | |
| else | |
| local route_name = name:match("link_([%w_]+)") | |
| if route_name then | |
| func = function (self, query, ...) | |
| return tab:route_link(route_name, query, ...) | |
| end | |
| end | |
| end | |
| tab[name] = func | |
| return func | |
| end | |
| function methods:qs_encode(query, url) | |
| local parts = {} | |
| for k, v in pairs(query or {}) do | |
| parts[#parts+1] = k .. "=" .. util.url_encode(v) | |
| end | |
| if #parts > 0 then | |
| return (url and (url .. "?") or "") .. table.concat(parts, "&") | |
| else | |
| return (url and url or "") | |
| end | |
| end | |
| function methods:route_link(route, query, ...) | |
| local builder = self.mk_app["link_" .. route] | |
| if builder then | |
| local uri = builder(self.mk_app, self.env, ...) | |
| local qs = self:qs_encode(query) | |
| return uri .. (qs ~= "" and ("?"..qs) or "") | |
| else | |
| error("there is no route named " .. route) | |
| end | |
| end | |
| function methods:link(url, query) | |
| local prefix = (self.mk_app and self.mk_app.prefix) or self.script_name | |
| local uri = prefix .. url | |
| local qs = self:qs_encode(query) | |
| return prefix .. url .. (qs ~= "" and ("?"..qs) or "") | |
| end | |
| function methods:absolute_link(url, query) | |
| local qs = self:qs_encode(query) | |
| return url .. (qs ~= "" and ("?"..qs) or "") | |
| end | |
| function methods:static_link(url) | |
| local prefix = (self.mk_app and self.mk_app.prefix) or self.script_name | |
| local is_script = prefix:match("(%.%w+)$") | |
| if not is_script then return self:link(url) end | |
| local vpath = prefix:match("(.*)/") or "" | |
| return vpath .. url | |
| end | |
| function methods:empty(s) | |
| return not s or string.match(s, "^%s*$") | |
| end | |
| function methods:empty_param(param) | |
| return self:empty(self.params[param]) | |
| end | |
| function _M.new(wsapi_env, options) | |
| options = options or {} | |
| local req = { | |
| GET = {}, | |
| POST = {}, | |
| method = wsapi_env.REQUEST_METHOD, | |
| path_info = wsapi_env.PATH_INFO, | |
| query_string = wsapi_env.QUERY_STRING, | |
| script_name = wsapi_env.SCRIPT_NAME, | |
| env = wsapi_env, | |
| mk_app = options.mk_app, | |
| doc_root = wsapi_env.DOCUMENT_ROOT, | |
| app_path = wsapi_env.APP_PATH | |
| } | |
| parse_qs(wsapi_env.QUERY_STRING, req.GET, options.overwrite) | |
| if options.delay_post then | |
| req.parse_post = function (self) | |
| parse_post_data(wsapi_env, self.POST, options.overwrite) | |
| self.parse_post = function () return nil, "postdata already parsed" end | |
| return self.POST | |
| end | |
| else | |
| parse_post_data(wsapi_env, req.POST, options.overwrite) | |
| req.parse_post = function () return nil, "postdata already parsed" end | |
| end | |
| req.params = {} | |
| setmetatable(req.params, { __index = function (tab, name) | |
| local var = req.GET[name] or req.POST[name] | |
| rawset(tab, name, var) | |
| return var | |
| end}) | |
| req.cookies = {} | |
| local cookies = string.gsub(";" .. (wsapi_env.HTTP_COOKIE or "") .. ";", | |
| "%s*;%s*", ";") | |
| setmetatable(req.cookies, { __index = function (tab, name) | |
| name = name | |
| local pattern = ";" .. name .. | |
| "=(.-);" | |
| local cookie = string.match(cookies, pattern) | |
| cookie = util.url_decode(cookie) | |
| rawset(tab, name, cookie) | |
| return cookie | |
| end}) | |
| return setmetatable(req, methods) | |
| end | |
| return _M |