Merge pull request #563 from cshore/pull-request-app-uhttpd
[project/luci.git] / modules / luci-base / luasrc / http.lua
1 -- Copyright 2008 Steven Barth <steven@midlink.org>
2 -- Licensed to the public under the Apache License 2.0.
3
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
11 local ipairs, pairs, next, type, tostring, error =
12 ipairs, pairs, next, type, tostring, error
13
14 module "luci.http"
15
16 context = util.threadlocal()
17
18 Request = util.class()
19 function Request.__init__(self, env, sourcein, sinkerr)
20 self.input = sourcein
21 self.error = sinkerr
22
23
24 -- File handler nil by default to let .content() work
25 self.filehandler = nil
26
27 -- HTTP-Message table
28 self.message = {
29 env = env,
30 headers = {},
31 params = protocol.urldecode_params(env.QUERY_STRING or ""),
32 }
33
34 self.parsed_input = false
35 end
36
37 function Request.formvalue(self, name, noparse)
38 if not noparse and not self.parsed_input then
39 self:_parse_input()
40 end
41
42 if name then
43 return self.message.params[name]
44 else
45 return self.message.params
46 end
47 end
48
49 function Request.formvaluetable(self, prefix)
50 local vals = {}
51 prefix = prefix and prefix .. "." or "."
52
53 if not self.parsed_input then
54 self:_parse_input()
55 end
56
57 local void = self.message.params[nil]
58 for k, v in pairs(self.message.params) do
59 if k:find(prefix, 1, true) == 1 then
60 vals[k:sub(#prefix + 1)] = tostring(v)
61 end
62 end
63
64 return vals
65 end
66
67 function Request.content(self)
68 if not self.parsed_input then
69 self:_parse_input()
70 end
71
72 return self.message.content, self.message.content_length
73 end
74
75 function Request.getcookie(self, name)
76 local c = string.gsub(";" .. (self:getenv("HTTP_COOKIE") or "") .. ";", "%s*;%s*", ";")
77 local p = ";" .. name .. "=(.-);"
78 local i, j, value = c:find(p)
79 return value and urldecode(value)
80 end
81
82 function Request.getenv(self, name)
83 if name then
84 return self.message.env[name]
85 else
86 return self.message.env
87 end
88 end
89
90 function Request.setfilehandler(self, callback)
91 self.filehandler = callback
92
93 -- If input has already been parsed then any files are either in temporary files
94 -- or are in self.message.params[key]
95 if self.parsed_input then
96 for param, value in pairs(self.message.params) do
97 repeat
98 -- We're only interested in files
99 if (not value["file"]) then break end
100 -- If we were able to write to temporary file
101 if (value["fd"]) then
102 fd = value["fd"]
103 local eof = false
104 repeat
105 filedata = fd:read(1024)
106 if (filedata:len() < 1024) then
107 eof = true
108 end
109 callback({ name=value["name"], file=value["file"] }, filedata, eof)
110 until (eof)
111 fd:close()
112 value["fd"] = nil
113 -- We had to read into memory
114 else
115 -- There should only be one numbered value in table - the data
116 for k, v in ipairs(value) do
117 callback({ name=value["name"], file=value["file"] }, v, true)
118 end
119 end
120 until true
121 end
122 end
123 end
124
125 function Request._parse_input(self)
126 protocol.parse_message_body(
127 self.input,
128 self.message,
129 self.filehandler
130 )
131 self.parsed_input = true
132 end
133
134 function close()
135 if not context.eoh then
136 context.eoh = true
137 coroutine.yield(3)
138 end
139
140 if not context.closed then
141 context.closed = true
142 coroutine.yield(5)
143 end
144 end
145
146 function content()
147 return context.request:content()
148 end
149
150 function formvalue(name, noparse)
151 return context.request:formvalue(name, noparse)
152 end
153
154 function formvaluetable(prefix)
155 return context.request:formvaluetable(prefix)
156 end
157
158 function getcookie(name)
159 return context.request:getcookie(name)
160 end
161
162 -- or the environment table itself.
163 function getenv(name)
164 return context.request:getenv(name)
165 end
166
167 function setfilehandler(callback)
168 return context.request:setfilehandler(callback)
169 end
170
171 function header(key, value)
172 if not context.headers then
173 context.headers = {}
174 end
175 context.headers[key:lower()] = value
176 coroutine.yield(2, key, value)
177 end
178
179 function prepare_content(mime)
180 if not context.headers or not context.headers["content-type"] then
181 if mime == "application/xhtml+xml" then
182 if not getenv("HTTP_ACCEPT") or
183 not getenv("HTTP_ACCEPT"):find("application/xhtml+xml", nil, true) then
184 mime = "text/html; charset=UTF-8"
185 end
186 header("Vary", "Accept")
187 end
188 header("Content-Type", mime)
189 end
190 end
191
192 function source()
193 return context.request.input
194 end
195
196 function status(code, message)
197 code = code or 200
198 message = message or "OK"
199 context.status = code
200 coroutine.yield(1, code, message)
201 end
202
203 -- This function is as a valid LTN12 sink.
204 -- If the content chunk is nil this function will automatically invoke close.
205 function write(content, src_err)
206 if not content then
207 if src_err then
208 error(src_err)
209 else
210 close()
211 end
212 return true
213 elseif #content == 0 then
214 return true
215 else
216 if not context.eoh then
217 if not context.status then
218 status()
219 end
220 if not context.headers or not context.headers["content-type"] then
221 header("Content-Type", "text/html; charset=utf-8")
222 end
223 if not context.headers["cache-control"] then
224 header("Cache-Control", "no-cache")
225 header("Expires", "0")
226 end
227
228
229 context.eoh = true
230 coroutine.yield(3)
231 end
232 coroutine.yield(4, content)
233 return true
234 end
235 end
236
237 function splice(fd, size)
238 coroutine.yield(6, fd, size)
239 end
240
241 function redirect(url)
242 if url == "" then url = "/" end
243 status(302, "Found")
244 header("Location", url)
245 close()
246 end
247
248 function build_querystring(q)
249 local s = { "?" }
250
251 for k, v in pairs(q) do
252 if #s > 1 then s[#s+1] = "&" end
253
254 s[#s+1] = urldecode(k)
255 s[#s+1] = "="
256 s[#s+1] = urldecode(v)
257 end
258
259 return table.concat(s, "")
260 end
261
262 urldecode = protocol.urldecode
263
264 urlencode = protocol.urlencode
265
266 function write_json(x)
267 util.serialize_json(x, write)
268 end