1 -- Copyright 2008 Steven Barth <steven@midlink.org>
2 -- Licensed to the public under the Apache License 2.0.
4 local ltn12 = require "luci.ltn12"
5 local protocol = require "luci.http.protocol"
6 local util = require "luci.util"
7 local string = require "string"
8 local coroutine = require "coroutine"
9 local table = require "table"
10 local lhttp = require "lucihttp"
12 local ipairs, pairs, next, type, tostring, error =
13 ipairs, pairs, next, type, tostring, error
17 context = util.threadlocal()
19 Request = util.class()
20 function Request.__init__(self, env, sourcein, sinkerr)
25 -- File handler nil by default to let .content() work
26 self.filehandler = nil
32 params = protocol.urldecode_params(env.QUERY_STRING or ""),
35 self.parsed_input = false
38 function Request.formvalue(self, name, noparse)
39 if not noparse and not self.parsed_input then
44 return self.message.params[name]
46 return self.message.params
50 function Request.formvaluetable(self, prefix)
52 prefix = prefix and prefix .. "." or "."
54 if not self.parsed_input then
58 local void = self.message.params[nil]
59 for k, v in pairs(self.message.params) do
60 if k:find(prefix, 1, true) == 1 then
61 vals[k:sub(#prefix + 1)] = tostring(v)
68 function Request.content(self)
69 if not self.parsed_input then
73 return self.message.content, self.message.content_length
76 function Request.getcookie(self, name)
77 return lhttp.header_attribute("cookie; " .. (self:getenv("HTTP_COOKIE") or ""), name)
80 function Request.getenv(self, name)
82 return self.message.env[name]
84 return self.message.env
88 function Request.setfilehandler(self, callback)
89 self.filehandler = callback
91 if not self.parsed_input then
95 -- If input has already been parsed then uploads are stored as unlinked
96 -- temporary files pointed to by open file handles in the parameter
97 -- value table. Loop all params, and invoke the file callback for any
98 -- param with an open file handle.
100 for name, value in pairs(self.message.params) do
101 if type(value) == "table" then
103 local data = value.fd:read(1024)
104 local eof = (not data or data == "")
106 callback(value, data, eof)
117 function Request._parse_input(self)
118 protocol.parse_message_body(
123 self.parsed_input = true
127 if not context.eoh then
132 if not context.closed then
133 context.closed = true
139 return context.request:content()
142 function formvalue(name, noparse)
143 return context.request:formvalue(name, noparse)
146 function formvaluetable(prefix)
147 return context.request:formvaluetable(prefix)
150 function getcookie(name)
151 return context.request:getcookie(name)
154 -- or the environment table itself.
155 function getenv(name)
156 return context.request:getenv(name)
159 function setfilehandler(callback)
160 return context.request:setfilehandler(callback)
163 function header(key, value)
164 if not context.headers then
167 context.headers[key:lower()] = value
168 coroutine.yield(2, key, value)
171 function prepare_content(mime)
172 if not context.headers or not context.headers["content-type"] then
173 if mime == "application/xhtml+xml" then
174 if not getenv("HTTP_ACCEPT") or
175 not getenv("HTTP_ACCEPT"):find("application/xhtml+xml", nil, true) then
176 mime = "text/html; charset=UTF-8"
178 header("Vary", "Accept")
180 header("Content-Type", mime)
185 return context.request.input
188 function status(code, message)
190 message = message or "OK"
191 context.status = code
192 coroutine.yield(1, code, message)
195 -- This function is as a valid LTN12 sink.
196 -- If the content chunk is nil this function will automatically invoke close.
197 function write(content, src_err)
205 elseif #content == 0 then
208 if not context.eoh then
209 if not context.status then
212 if not context.headers or not context.headers["content-type"] then
213 header("Content-Type", "text/html; charset=utf-8")
215 if not context.headers["cache-control"] then
216 header("Cache-Control", "no-cache")
217 header("Expires", "0")
219 if not context.headers["x-frame-options"] then
220 header("X-Frame-Options", "SAMEORIGIN")
222 if not context.headers["x-xss-protection"] then
223 header("X-XSS-Protection", "1; mode=block")
225 if not context.headers["x-content-type-options"] then
226 header("X-Content-Type-Options", "nosniff")
232 coroutine.yield(4, content)
237 function splice(fd, size)
238 coroutine.yield(6, fd, size)
241 function redirect(url)
242 if url == "" then url = "/" end
244 header("Location", url)
248 function build_querystring(q)
249 local s, n, k, v = {}, 1, nil, nil
251 for k, v in pairs(q) do
252 s[n+0] = (n == 1) and "?" or "&"
253 s[n+1] = util.urlencode(k)
255 s[n+3] = util.urlencode(v)
259 return table.concat(s, "")
262 urldecode = util.urldecode
264 urlencode = util.urlencode
266 function write_json(x)
267 util.serialize_json(x, write)