| 1 | --[[ |
|---|
| 2 | LuCI - HTTP-Interaction |
|---|
| 3 | |
|---|
| 4 | Description: |
|---|
| 5 | HTTP-Header manipulator and form variable preprocessor |
|---|
| 6 | |
|---|
| 7 | FileId: |
|---|
| 8 | $Id$ |
|---|
| 9 | |
|---|
| 10 | License: |
|---|
| 11 | Copyright 2008 Steven Barth <steven@midlink.org> |
|---|
| 12 | |
|---|
| 13 | Licensed under the Apache License, Version 2.0 (the "License"); |
|---|
| 14 | you may not use this file except in compliance with the License. |
|---|
| 15 | You may obtain a copy of the License at |
|---|
| 16 | |
|---|
| 17 | http://www.apache.org/licenses/LICENSE-2.0 |
|---|
| 18 | |
|---|
| 19 | Unless required by applicable law or agreed to in writing, software |
|---|
| 20 | distributed under the License is distributed on an "AS IS" BASIS, |
|---|
| 21 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|---|
| 22 | See the License for the specific language governing permissions and |
|---|
| 23 | limitations under the License. |
|---|
| 24 | |
|---|
| 25 | ]]-- |
|---|
| 26 | |
|---|
| 27 | local ltn12 = require "luci.ltn12" |
|---|
| 28 | local protocol = require "luci.http.protocol" |
|---|
| 29 | local util = require "luci.util" |
|---|
| 30 | local string = require "string" |
|---|
| 31 | local coroutine = require "coroutine" |
|---|
| 32 | local table = require "table" |
|---|
| 33 | |
|---|
| 34 | local ipairs, pairs, next, type, tostring, error = |
|---|
| 35 | ipairs, pairs, next, type, tostring, error |
|---|
| 36 | |
|---|
| 37 | --- LuCI Web Framework high-level HTTP functions. |
|---|
| 38 | module "luci.http" |
|---|
| 39 | |
|---|
| 40 | context = util.threadlocal() |
|---|
| 41 | |
|---|
| 42 | Request = util.class() |
|---|
| 43 | function Request.__init__(self, env, sourcein, sinkerr) |
|---|
| 44 | self.input = sourcein |
|---|
| 45 | self.error = sinkerr |
|---|
| 46 | |
|---|
| 47 | |
|---|
| 48 | -- File handler |
|---|
| 49 | self.filehandler = function() end |
|---|
| 50 | |
|---|
| 51 | -- HTTP-Message table |
|---|
| 52 | self.message = { |
|---|
| 53 | env = env, |
|---|
| 54 | headers = {}, |
|---|
| 55 | params = protocol.urldecode_params(env.QUERY_STRING or ""), |
|---|
| 56 | } |
|---|
| 57 | |
|---|
| 58 | self.parsed_input = false |
|---|
| 59 | end |
|---|
| 60 | |
|---|
| 61 | function Request.formvalue(self, name, noparse) |
|---|
| 62 | if not noparse and not self.parsed_input then |
|---|
| 63 | self:_parse_input() |
|---|
| 64 | end |
|---|
| 65 | |
|---|
| 66 | if name then |
|---|
| 67 | return self.message.params[name] |
|---|
| 68 | else |
|---|
| 69 | return self.message.params |
|---|
| 70 | end |
|---|
| 71 | end |
|---|
| 72 | |
|---|
| 73 | function Request.formvaluetable(self, prefix) |
|---|
| 74 | local vals = {} |
|---|
| 75 | prefix = prefix and prefix .. "." or "." |
|---|
| 76 | |
|---|
| 77 | if not self.parsed_input then |
|---|
| 78 | self:_parse_input() |
|---|
| 79 | end |
|---|
| 80 | |
|---|
| 81 | local void = self.message.params[nil] |
|---|
| 82 | for k, v in pairs(self.message.params) do |
|---|
| 83 | if k:find(prefix, 1, true) == 1 then |
|---|
| 84 | vals[k:sub(#prefix + 1)] = tostring(v) |
|---|
| 85 | end |
|---|
| 86 | end |
|---|
| 87 | |
|---|
| 88 | return vals |
|---|
| 89 | end |
|---|
| 90 | |
|---|
| 91 | function Request.content(self) |
|---|
| 92 | if not self.parsed_input then |
|---|
| 93 | self:_parse_input() |
|---|
| 94 | end |
|---|
| 95 | |
|---|
| 96 | return self.message.content, self.message.content_length |
|---|
| 97 | end |
|---|
| 98 | |
|---|
| 99 | function Request.getcookie(self, name) |
|---|
| 100 | local c = string.gsub(";" .. (self:getenv("HTTP_COOKIE") or "") .. ";", "%s*;%s*", ";") |
|---|
| 101 | local p = ";" .. name .. "=(.-);" |
|---|
| 102 | local i, j, value = c:find(p) |
|---|
| 103 | return value and urldecode(value) |
|---|
| 104 | end |
|---|
| 105 | |
|---|
| 106 | function Request.getenv(self, name) |
|---|
| 107 | if name then |
|---|
| 108 | return self.message.env[name] |
|---|
| 109 | else |
|---|
| 110 | return self.message.env |
|---|
| 111 | end |
|---|
| 112 | end |
|---|
| 113 | |
|---|
| 114 | function Request.setfilehandler(self, callback) |
|---|
| 115 | self.filehandler = callback |
|---|
| 116 | end |
|---|
| 117 | |
|---|
| 118 | function Request._parse_input(self) |
|---|
| 119 | protocol.parse_message_body( |
|---|
| 120 | self.input, |
|---|
| 121 | self.message, |
|---|
| 122 | self.filehandler |
|---|
| 123 | ) |
|---|
| 124 | self.parsed_input = true |
|---|
| 125 | end |
|---|
| 126 | |
|---|
| 127 | --- Close the HTTP-Connection. |
|---|
| 128 | function close() |
|---|
| 129 | if not context.eoh then |
|---|
| 130 | context.eoh = true |
|---|
| 131 | coroutine.yield(3) |
|---|
| 132 | end |
|---|
| 133 | |
|---|
| 134 | if not context.closed then |
|---|
| 135 | context.closed = true |
|---|
| 136 | coroutine.yield(5) |
|---|
| 137 | end |
|---|
| 138 | end |
|---|
| 139 | |
|---|
| 140 | --- Return the request content if the request was of unknown type. |
|---|
| 141 | -- @return HTTP request body |
|---|
| 142 | -- @return HTTP request body length |
|---|
| 143 | function content() |
|---|
| 144 | return context.request:content() |
|---|
| 145 | end |
|---|
| 146 | |
|---|
| 147 | --- Get a certain HTTP input value or a table of all input values. |
|---|
| 148 | -- @param name Name of the GET or POST variable to fetch |
|---|
| 149 | -- @param noparse Don't parse POST data before getting the value |
|---|
| 150 | -- @return HTTP input value or table of all input value |
|---|
| 151 | function formvalue(name, noparse) |
|---|
| 152 | return context.request:formvalue(name, noparse) |
|---|
| 153 | end |
|---|
| 154 | |
|---|
| 155 | --- Get a table of all HTTP input values with a certain prefix. |
|---|
| 156 | -- @param prefix Prefix |
|---|
| 157 | -- @return Table of all HTTP input values with given prefix |
|---|
| 158 | function formvaluetable(prefix) |
|---|
| 159 | return context.request:formvaluetable(prefix) |
|---|
| 160 | end |
|---|
| 161 | |
|---|
| 162 | --- Get the value of a certain HTTP-Cookie. |
|---|
| 163 | -- @param name Cookie Name |
|---|
| 164 | -- @return String containing cookie data |
|---|
| 165 | function getcookie(name) |
|---|
| 166 | return context.request:getcookie(name) |
|---|
| 167 | end |
|---|
| 168 | |
|---|
| 169 | --- Get the value of a certain HTTP environment variable |
|---|
| 170 | -- or the environment table itself. |
|---|
| 171 | -- @param name Environment variable |
|---|
| 172 | -- @return HTTP environment value or environment table |
|---|
| 173 | function getenv(name) |
|---|
| 174 | return context.request:getenv(name) |
|---|
| 175 | end |
|---|
| 176 | |
|---|
| 177 | --- Set a handler function for incoming user file uploads. |
|---|
| 178 | -- @param callback Handler function |
|---|
| 179 | function setfilehandler(callback) |
|---|
| 180 | return context.request:setfilehandler(callback) |
|---|
| 181 | end |
|---|
| 182 | |
|---|
| 183 | --- Send a HTTP-Header. |
|---|
| 184 | -- @param key Header key |
|---|
| 185 | -- @param value Header value |
|---|
| 186 | function header(key, value) |
|---|
| 187 | if not context.headers then |
|---|
| 188 | context.headers = {} |
|---|
| 189 | end |
|---|
| 190 | context.headers[key:lower()] = value |
|---|
| 191 | coroutine.yield(2, key, value) |
|---|
| 192 | end |
|---|
| 193 | |
|---|
| 194 | --- Set the mime type of following content data. |
|---|
| 195 | -- @param mime Mimetype of following content |
|---|
| 196 | function prepare_content(mime) |
|---|
| 197 | if not context.headers or not context.headers["content-type"] then |
|---|
| 198 | if mime == "application/xhtml+xml" then |
|---|
| 199 | if not getenv("HTTP_ACCEPT") or |
|---|
| 200 | not getenv("HTTP_ACCEPT"):find("application/xhtml+xml", nil, true) then |
|---|
| 201 | mime = "text/html; charset=UTF-8" |
|---|
| 202 | end |
|---|
| 203 | header("Vary", "Accept") |
|---|
| 204 | end |
|---|
| 205 | header("Content-Type", mime) |
|---|
| 206 | end |
|---|
| 207 | end |
|---|
| 208 | |
|---|
| 209 | --- Get the RAW HTTP input source |
|---|
| 210 | -- @return HTTP LTN12 source |
|---|
| 211 | function source() |
|---|
| 212 | return context.request.input |
|---|
| 213 | end |
|---|
| 214 | |
|---|
| 215 | --- Set the HTTP status code and status message. |
|---|
| 216 | -- @param code Status code |
|---|
| 217 | -- @param message Status message |
|---|
| 218 | function status(code, message) |
|---|
| 219 | code = code or 200 |
|---|
| 220 | message = message or "OK" |
|---|
| 221 | context.status = code |
|---|
| 222 | coroutine.yield(1, code, message) |
|---|
| 223 | end |
|---|
| 224 | |
|---|
| 225 | --- Send a chunk of content data to the client. |
|---|
| 226 | -- This function is as a valid LTN12 sink. |
|---|
| 227 | -- If the content chunk is nil this function will automatically invoke close. |
|---|
| 228 | -- @param content Content chunk |
|---|
| 229 | -- @param src_err Error object from source (optional) |
|---|
| 230 | -- @see close |
|---|
| 231 | function write(content, src_err) |
|---|
| 232 | if not content then |
|---|
| 233 | if src_err then |
|---|
| 234 | error(src_err) |
|---|
| 235 | else |
|---|
| 236 | close() |
|---|
| 237 | end |
|---|
| 238 | return true |
|---|
| 239 | elseif #content == 0 then |
|---|
| 240 | return true |
|---|
| 241 | else |
|---|
| 242 | if not context.eoh then |
|---|
| 243 | if not context.status then |
|---|
| 244 | status() |
|---|
| 245 | end |
|---|
| 246 | if not context.headers or not context.headers["content-type"] then |
|---|
| 247 | header("Content-Type", "text/html; charset=utf-8") |
|---|
| 248 | end |
|---|
| 249 | if not context.headers["cache-control"] then |
|---|
| 250 | header("Cache-Control", "no-cache") |
|---|
| 251 | header("Expires", "0") |
|---|
| 252 | end |
|---|
| 253 | |
|---|
| 254 | |
|---|
| 255 | context.eoh = true |
|---|
| 256 | coroutine.yield(3) |
|---|
| 257 | end |
|---|
| 258 | coroutine.yield(4, content) |
|---|
| 259 | return true |
|---|
| 260 | end |
|---|
| 261 | end |
|---|
| 262 | |
|---|
| 263 | --- Splice data from a filedescriptor to the client. |
|---|
| 264 | -- @param fp File descriptor |
|---|
| 265 | -- @param size Bytes to splice (optional) |
|---|
| 266 | function splice(fd, size) |
|---|
| 267 | coroutine.yield(6, fd, size) |
|---|
| 268 | end |
|---|
| 269 | |
|---|
| 270 | --- Redirects the client to a new URL and closes the connection. |
|---|
| 271 | -- @param url Target URL |
|---|
| 272 | function redirect(url) |
|---|
| 273 | status(302, "Found") |
|---|
| 274 | header("Location", url) |
|---|
| 275 | close() |
|---|
| 276 | end |
|---|
| 277 | |
|---|
| 278 | --- Create a querystring out of a table of key - value pairs. |
|---|
| 279 | -- @param table Query string source table |
|---|
| 280 | -- @return Encoded HTTP query string |
|---|
| 281 | function build_querystring(q) |
|---|
| 282 | local s = { "?" } |
|---|
| 283 | |
|---|
| 284 | for k, v in pairs(q) do |
|---|
| 285 | if #s > 1 then s[#s+1] = "&" end |
|---|
| 286 | |
|---|
| 287 | s[#s+1] = urldecode(k) |
|---|
| 288 | s[#s+1] = "=" |
|---|
| 289 | s[#s+1] = urldecode(v) |
|---|
| 290 | end |
|---|
| 291 | |
|---|
| 292 | return table.concat(s, "") |
|---|
| 293 | end |
|---|
| 294 | |
|---|
| 295 | --- Return the URL-decoded equivalent of a string. |
|---|
| 296 | -- @param str URL-encoded string |
|---|
| 297 | -- @param no_plus Don't decode + to " " |
|---|
| 298 | -- @return URL-decoded string |
|---|
| 299 | -- @see urlencode |
|---|
| 300 | urldecode = protocol.urldecode |
|---|
| 301 | |
|---|
| 302 | --- Return the URL-encoded equivalent of a string. |
|---|
| 303 | -- @param str Source string |
|---|
| 304 | -- @return URL-encoded string |
|---|
| 305 | -- @see urldecode |
|---|
| 306 | urlencode = protocol.urlencode |
|---|
| 307 | |
|---|
| 308 | --- Send the given data as JSON encoded string. |
|---|
| 309 | -- @param data Data to send |
|---|
| 310 | function write_json(x) |
|---|
| 311 | if x == nil then |
|---|
| 312 | write("null") |
|---|
| 313 | elseif type(x) == "table" then |
|---|
| 314 | local k, v |
|---|
| 315 | if type(next(x)) == "number" then |
|---|
| 316 | write("[ ") |
|---|
| 317 | for k, v in ipairs(x) do |
|---|
| 318 | write_json(v) |
|---|
| 319 | if next(x, k) then |
|---|
| 320 | write(", ") |
|---|
| 321 | end |
|---|
| 322 | end |
|---|
| 323 | write(" ]") |
|---|
| 324 | else |
|---|
| 325 | write("{ ") |
|---|
| 326 | for k, v in pairs(x) do |
|---|
| 327 | write("%q: " % k) |
|---|
| 328 | write_json(v) |
|---|
| 329 | if next(x, k) then |
|---|
| 330 | write(", ") |
|---|
| 331 | end |
|---|
| 332 | end |
|---|
| 333 | write(" }") |
|---|
| 334 | end |
|---|
| 335 | elseif type(x) == "number" or type(x) == "boolean" then |
|---|
| 336 | write(tostring(x)) |
|---|
| 337 | elseif type(x) == "string" then |
|---|
| 338 | write("%q" % tostring(x)) |
|---|
| 339 | end |
|---|
| 340 | end |
|---|