Merge pull request #304 from nmav/ocserv-crypt
[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 status(302, "Found")
212 header("Location", url)
213 close()
214 end
215
216 function build_querystring(q)
217 local s = { "?" }
218
219 for k, v in pairs(q) do
220 if #s > 1 then s[#s+1] = "&" end
221
222 s[#s+1] = urldecode(k)
223 s[#s+1] = "="
224 s[#s+1] = urldecode(v)
225 end
226
227 return table.concat(s, "")
228 end
229
230 urldecode = protocol.urldecode
231
232 urlencode = protocol.urlencode
233
234 function write_json(x)
235 util.serialize_json(x, write)
236 end