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