-- Copyright 2008 Steven Barth -- Licensed to the public under the Apache License 2.0. local ltn12 = require "luci.ltn12" local protocol = require "luci.http.protocol" local util = require "luci.util" local string = require "string" local coroutine = require "coroutine" local table = require "table" local ipairs, pairs, next, type, tostring, error = ipairs, pairs, next, type, tostring, error --- LuCI Web Framework high-level HTTP functions. module "luci.http" context = util.threadlocal() Request = util.class() function Request.__init__(self, env, sourcein, sinkerr) self.input = sourcein self.error = sinkerr -- File handler nil by default to let .content() work self.filehandler = nil -- HTTP-Message table self.message = { env = env, headers = {}, params = protocol.urldecode_params(env.QUERY_STRING or ""), } self.parsed_input = false end function Request.formvalue(self, name, noparse) if not noparse and not self.parsed_input then self:_parse_input() end if name then return self.message.params[name] else return self.message.params end end function Request.formvaluetable(self, prefix) local vals = {} prefix = prefix and prefix .. "." or "." if not self.parsed_input then self:_parse_input() end local void = self.message.params[nil] for k, v in pairs(self.message.params) do if k:find(prefix, 1, true) == 1 then vals[k:sub(#prefix + 1)] = tostring(v) end end return vals end function Request.content(self) if not self.parsed_input then self:_parse_input() end return self.message.content, self.message.content_length end function Request.getcookie(self, name) local c = string.gsub(";" .. (self:getenv("HTTP_COOKIE") or "") .. ";", "%s*;%s*", ";") local p = ";" .. name .. "=(.-);" local i, j, value = c:find(p) return value and urldecode(value) end function Request.getenv(self, name) if name then return self.message.env[name] else return self.message.env end end function Request.setfilehandler(self, callback) self.filehandler = callback end function Request._parse_input(self) protocol.parse_message_body( self.input, self.message, self.filehandler ) self.parsed_input = true end --- Close the HTTP-Connection. function close() if not context.eoh then context.eoh = true coroutine.yield(3) end if not context.closed then context.closed = true coroutine.yield(5) end end --- Return the request content if the request was of unknown type. -- @return HTTP request body -- @return HTTP request body length function content() return context.request:content() end --- Get a certain HTTP input value or a table of all input values. -- @param name Name of the GET or POST variable to fetch -- @param noparse Don't parse POST data before getting the value -- @return HTTP input value or table of all input value function formvalue(name, noparse) return context.request:formvalue(name, noparse) end --- Get a table of all HTTP input values with a certain prefix. -- @param prefix Prefix -- @return Table of all HTTP input values with given prefix function formvaluetable(prefix) return context.request:formvaluetable(prefix) end --- Get the value of a certain HTTP-Cookie. -- @param name Cookie Name -- @return String containing cookie data function getcookie(name) return context.request:getcookie(name) end --- Get the value of a certain HTTP environment variable -- or the environment table itself. -- @param name Environment variable -- @return HTTP environment value or environment table function getenv(name) return context.request:getenv(name) end --- Set a handler function for incoming user file uploads. -- @param callback Handler function function setfilehandler(callback) return context.request:setfilehandler(callback) end --- Send a HTTP-Header. -- @param key Header key -- @param value Header value function header(key, value) if not context.headers then context.headers = {} end context.headers[key:lower()] = value coroutine.yield(2, key, value) end --- Set the mime type of following content data. -- @param mime Mimetype of following content function prepare_content(mime) if not context.headers or not context.headers["content-type"] then if mime == "application/xhtml+xml" then if not getenv("HTTP_ACCEPT") or not getenv("HTTP_ACCEPT"):find("application/xhtml+xml", nil, true) then mime = "text/html; charset=UTF-8" end header("Vary", "Accept") end header("Content-Type", mime) end end --- Get the RAW HTTP input source -- @return HTTP LTN12 source function source() return context.request.input end --- Set the HTTP status code and status message. -- @param code Status code -- @param message Status message function status(code, message) code = code or 200 message = message or "OK" context.status = code coroutine.yield(1, code, message) end --- Send a chunk of content data to the client. -- This function is as a valid LTN12 sink. -- If the content chunk is nil this function will automatically invoke close. -- @param content Content chunk -- @param src_err Error object from source (optional) -- @see close function write(content, src_err) if not content then if src_err then error(src_err) else close() end return true elseif #content == 0 then return true else if not context.eoh then if not context.status then status() end if not context.headers or not context.headers["content-type"] then header("Content-Type", "text/html; charset=utf-8") end if not context.headers["cache-control"] then header("Cache-Control", "no-cache") header("Expires", "0") end context.eoh = true coroutine.yield(3) end coroutine.yield(4, content) return true end end --- Splice data from a filedescriptor to the client. -- @param fp File descriptor -- @param size Bytes to splice (optional) function splice(fd, size) coroutine.yield(6, fd, size) end --- Redirects the client to a new URL and closes the connection. -- @param url Target URL function redirect(url) status(302, "Found") header("Location", url) close() end --- Create a querystring out of a table of key - value pairs. -- @param table Query string source table -- @return Encoded HTTP query string function build_querystring(q) local s = { "?" } for k, v in pairs(q) do if #s > 1 then s[#s+1] = "&" end s[#s+1] = urldecode(k) s[#s+1] = "=" s[#s+1] = urldecode(v) end return table.concat(s, "") end --- Return the URL-decoded equivalent of a string. -- @param str URL-encoded string -- @param no_plus Don't decode + to " " -- @return URL-decoded string -- @see urlencode urldecode = protocol.urldecode --- Return the URL-encoded equivalent of a string. -- @param str Source string -- @return URL-encoded string -- @see urldecode urlencode = protocol.urlencode --- Send the given data as JSON encoded string. -- @param data Data to send function write_json(x) util.serialize_json(x, write) end