Merge pull request #462 from jplitza/jsonc-sink
[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 end
93
94 function Request._parse_input(self)
95         protocol.parse_message_body(
96                  self.input,
97                  self.message,
98                  self.filehandler
99         )
100         self.parsed_input = true
101 end
102
103 function close()
104         if not context.eoh then
105                 context.eoh = true
106                 coroutine.yield(3)
107         end
108
109         if not context.closed then
110                 context.closed = true
111                 coroutine.yield(5)
112         end
113 end
114
115 function content()
116         return context.request:content()
117 end
118
119 function formvalue(name, noparse)
120         return context.request:formvalue(name, noparse)
121 end
122
123 function formvaluetable(prefix)
124         return context.request:formvaluetable(prefix)
125 end
126
127 function getcookie(name)
128         return context.request:getcookie(name)
129 end
130
131 -- or the environment table itself.
132 function getenv(name)
133         return context.request:getenv(name)
134 end
135
136 function setfilehandler(callback)
137         return context.request:setfilehandler(callback)
138 end
139
140 function header(key, value)
141         if not context.headers then
142                 context.headers = {}
143         end
144         context.headers[key:lower()] = value
145         coroutine.yield(2, key, value)
146 end
147
148 function prepare_content(mime)
149         if not context.headers or not context.headers["content-type"] then
150                 if mime == "application/xhtml+xml" then
151                         if not getenv("HTTP_ACCEPT") or
152                           not getenv("HTTP_ACCEPT"):find("application/xhtml+xml", nil, true) then
153                                 mime = "text/html; charset=UTF-8"
154                         end
155                         header("Vary", "Accept")
156                 end
157                 header("Content-Type", mime)
158         end
159 end
160
161 function source()
162         return context.request.input
163 end
164
165 function status(code, message)
166         code = code or 200
167         message = message or "OK"
168         context.status = code
169         coroutine.yield(1, code, message)
170 end
171
172 -- This function is as a valid LTN12 sink.
173 -- If the content chunk is nil this function will automatically invoke close.
174 function write(content, src_err)
175         if not content then
176                 if src_err then
177                         error(src_err)
178                 else
179                         close()
180                 end
181                 return true
182         elseif #content == 0 then
183                 return true
184         else
185                 if not context.eoh then
186                         if not context.status then
187                                 status()
188                         end
189                         if not context.headers or not context.headers["content-type"] then
190                                 header("Content-Type", "text/html; charset=utf-8")
191                         end
192                         if not context.headers["cache-control"] then
193                                 header("Cache-Control", "no-cache")
194                                 header("Expires", "0")
195                         end
196
197
198                         context.eoh = true
199                         coroutine.yield(3)
200                 end
201                 coroutine.yield(4, content)
202                 return true
203         end
204 end
205
206 function splice(fd, size)
207         coroutine.yield(6, fd, size)
208 end
209
210 function redirect(url)
211         if url == "" then url = "/" end
212         status(302, "Found")
213         header("Location", url)
214         close()
215 end
216
217 function build_querystring(q)
218         local s = { "?" }
219
220         for k, v in pairs(q) do
221                 if #s > 1 then s[#s+1] = "&" end
222
223                 s[#s+1] = urldecode(k)
224                 s[#s+1] = "="
225                 s[#s+1] = urldecode(v)
226         end
227
228         return table.concat(s, "")
229 end
230
231 urldecode = protocol.urldecode
232
233 urlencode = protocol.urlencode
234
235 function write_json(x)
236         util.serialize_json(x, write)
237 end