luci-app-coovachilli: Updated my e-mail adress
[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