| 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 | |
|---|
| 33 | local pairs, tostring, error = pairs, tostring, error |
|---|
| 34 | |
|---|
| 35 | --- LuCI Web Framework high-level HTTP functions. |
|---|
| 36 | module "luci.http" |
|---|
| 37 | |
|---|
| 38 | context = util.threadlocal() |
|---|
| 39 | |
|---|
| 40 | Request = util.class() |
|---|
| 41 | function Request.__init__(self, env, sourcein, sinkerr) |
|---|
| 42 | self.input = sourcein |
|---|
| 43 | self.error = sinkerr |
|---|
| 44 | |
|---|
| 45 | |
|---|
| 46 | -- File handler |
|---|
| 47 | self.filehandler = function() end |
|---|
| 48 | |
|---|
| 49 | -- HTTP-Message table |
|---|
| 50 | self.message = { |
|---|
| 51 | env = env, |
|---|
| 52 | headers = {}, |
|---|
| 53 | params = protocol.urldecode_params(env.QUERY_STRING or ""), |
|---|
| 54 | } |
|---|
| 55 | |
|---|
| 56 | self.parsed_input = false |
|---|
| 57 | end |
|---|
| 58 | |
|---|
| 59 | function Request.formvalue(self, name, noparse) |
|---|
| 60 | if not noparse and not self.parsed_input then |
|---|
| 61 | self:_parse_input() |
|---|
| 62 | end |
|---|
| 63 | |
|---|
| 64 | if name then |
|---|
| 65 | return self.message.params[name] |
|---|
| 66 | else |
|---|
| 67 | return self.message.params |
|---|
| 68 | end |
|---|
| 69 | end |
|---|
| 70 | |
|---|
| 71 | function Request.formvaluetable(self, prefix) |
|---|
| 72 | local vals = {} |
|---|
| 73 | prefix = prefix and prefix .. "." or "." |
|---|
| 74 | |
|---|
| 75 | if not self.parsed_input then |
|---|
| 76 | self:_parse_input() |
|---|
| 77 | end |
|---|
| 78 | |
|---|
| 79 | local void = self.message.params[nil] |
|---|
| 80 | for k, v in pairs(self.message.params) do |
|---|
| 81 | if k:find(prefix, 1, true) == 1 then |
|---|
| 82 | vals[k:sub(#prefix + 1)] = tostring(v) |
|---|
| 83 | end |
|---|
| 84 | end |
|---|
| 85 | |
|---|
| 86 | return vals |
|---|
| 87 | end |
|---|
| 88 | |
|---|
| 89 | function Request.content(self) |
|---|
| 90 | if not self.parsed_input then |
|---|
| 91 | self:_parse_input() |
|---|
| 92 | end |
|---|
| 93 | |
|---|
| 94 | return self.message.content, self.message.content_length |
|---|
| 95 | end |
|---|
| 96 | |
|---|
| 97 | function Request.getcookie(self, name) |
|---|
| 98 | local c = string.gsub(";" .. (self:getenv("HTTP_COOKIE") or "") .. ";", "%s*;%s*", ";") |
|---|
| 99 | local p = ";" .. name .. "=(.-);" |
|---|
| 100 | local i, j, value = c:find(p) |
|---|
| 101 | return value and urldecode(value) |
|---|
| 102 | end |
|---|
| 103 | |
|---|
| 104 | function Request.getenv(self, name) |
|---|
| 105 | if name then |
|---|
| 106 | return self.message.env[name] |
|---|
| 107 | else |
|---|
| 108 | return self.message.env |
|---|
| 109 | end |
|---|
| 110 | end |
|---|
| 111 | |
|---|
| 112 | function Request.setfilehandler(self, callback) |
|---|
| 113 | self.filehandler = callback |
|---|
| 114 | end |
|---|
| 115 | |
|---|
| 116 | function Request._parse_input(self) |
|---|
| 117 | protocol.parse_message_body( |
|---|
| 118 | self.input, |
|---|
| 119 | self.message, |
|---|
| 120 | self.filehandler |
|---|
| 121 | ) |
|---|
| 122 | self.parsed_input = true |
|---|
| 123 | end |
|---|
| 124 | |
|---|
| 125 | --- Close the HTTP-Connection. |
|---|
| 126 | function close() |
|---|
| 127 | if not context.eoh then |
|---|
| 128 | context.eoh = true |
|---|
| 129 | coroutine.yield(3) |
|---|
| 130 | end |
|---|
| 131 | |
|---|
| 132 | if not context.closed then |
|---|
| 133 | context.closed = true |
|---|
| 134 | coroutine.yield(5) |
|---|
| 135 | end |
|---|
| 136 | end |
|---|
| 137 | |
|---|
| 138 | --- Return the request content if the request was of unknown type. |
|---|
| 139 | -- @return HTTP request body |
|---|
| 140 | -- @return HTTP request body length |
|---|
| 141 | function content() |
|---|
| 142 | return context.request:content() |
|---|
| 143 | end |
|---|
| 144 | |
|---|
| 145 | --- Get a certain HTTP input value or a table of all input values. |
|---|
| 146 | -- @param name Name of the GET or POST variable to fetch |
|---|
| 147 | -- @param noparse Don't parse POST data before getting the value |
|---|
| 148 | -- @return HTTP input value or table of all input value |
|---|
| 149 | function formvalue(name, noparse) |
|---|
| 150 | return context.request:formvalue(name, noparse) |
|---|
| 151 | end |
|---|
| 152 | |
|---|
| 153 | --- Get a table of all HTTP input values with a certain prefix. |
|---|
| 154 | -- @param prefix Prefix |
|---|
| 155 | -- @return Table of all HTTP input values with given prefix |
|---|
| 156 | function formvaluetable(prefix) |
|---|
| 157 | return context.request:formvaluetable(prefix) |
|---|
| 158 | end |
|---|
| 159 | |
|---|
| 160 | --- Get the value of a certain HTTP-Cookie. |
|---|
| 161 | -- @param name Cookie Name |
|---|
| 162 | -- @return String containing cookie data |
|---|
| 163 | function getcookie(name) |
|---|
| 164 | return context.request:getcookie(name) |
|---|
| 165 | end |
|---|
| 166 | |
|---|
| 167 | --- Get the value of a certain HTTP environment variable |
|---|
| 168 | -- or the environment table itself. |
|---|
| 169 | -- @param name Environment variable |
|---|
| 170 | -- @return HTTP environment value or environment table |
|---|
| 171 | function getenv(name) |
|---|
| 172 | return context.request:getenv(name) |
|---|
| 173 | end |
|---|
| 174 | |
|---|
| 175 | --- Set a handler function for incoming user file uploads. |
|---|
| 176 | -- @param callback Handler function |
|---|
| 177 | function setfilehandler(callback) |
|---|
| 178 | return context.request:setfilehandler(callback) |
|---|
| 179 | end |
|---|
| 180 | |
|---|
| 181 | --- Send a HTTP-Header. |
|---|
| 182 | -- @param key Header key |
|---|
| 183 | -- @param value Header value |
|---|
| 184 | function header(key, value) |
|---|
| 185 | if not context.headers then |
|---|
| 186 | context.headers = {} |
|---|
| 187 | end |
|---|
| 188 | context.headers[key:lower()] = value |
|---|
| 189 | coroutine.yield(2, key, value) |
|---|
| 190 | end |
|---|
| 191 | |
|---|
| 192 | --- Set the mime type of following content data. |
|---|
| 193 | -- @param mime Mimetype of following content |
|---|
| 194 | function prepare_content(mime) |
|---|
| 195 | if not context.headers or not context.headers["content-type"] then |
|---|
| 196 | if mime == "application/xhtml+xml" then |
|---|
| 197 | if not getenv("HTTP_ACCEPT") or |
|---|
| 198 | not getenv("HTTP_ACCEPT"):find("application/xhtml+xml", nil, true) then |
|---|
| 199 | mime = "text/html; charset=UTF-8" |
|---|
| 200 | end |
|---|
| 201 | header("Vary", "Accept") |
|---|
| 202 | end |
|---|
| 203 | header("Content-Type", mime) |
|---|
| 204 | end |
|---|
| 205 | end |
|---|
| 206 | |
|---|
| 207 | --- Get the RAW HTTP input source |
|---|
| 208 | -- @return HTTP LTN12 source |
|---|
| 209 | function source() |
|---|
| 210 | return context.request.input |
|---|
| 211 | end |
|---|
| 212 | |
|---|
| 213 | --- Set the HTTP status code and status message. |
|---|
| 214 | -- @param code Status code |
|---|
| 215 | -- @param message Status message |
|---|
| 216 | function status(code, message) |
|---|
| 217 | code = code or 200 |
|---|
| 218 | message = message or "OK" |
|---|
| 219 | context.status = code |
|---|
| 220 | coroutine.yield(1, code, message) |
|---|
| 221 | end |
|---|
| 222 | |
|---|
| 223 | --- Send a chunk of content data to the client. |
|---|
| 224 | -- This function is as a valid LTN12 sink. |
|---|
| 225 | -- If the content chunk is nil this function will automatically invoke close. |
|---|
| 226 | -- @param content Content chunk |
|---|
| 227 | -- @param src_err Error object from source (optional) |
|---|
| 228 | -- @see close |
|---|
| 229 | function write(content, src_err) |
|---|
| 230 | if not content then |
|---|
| 231 | if src_err then |
|---|
| 232 | error(src_err) |
|---|
| 233 | else |
|---|
| 234 | close() |
|---|
| 235 | end |
|---|
| 236 | return true |
|---|
| 237 | elseif #content == 0 then |
|---|
| 238 | return true |
|---|
| 239 | else |
|---|
| 240 | if not context.eoh then |
|---|
| 241 | if not context.status then |
|---|
| 242 | status() |
|---|
| 243 | end |
|---|
| 244 | if not context.headers or not context.headers["content-type"] then |
|---|
| 245 | header("Content-Type", "text/html; charset=utf-8") |
|---|
| 246 | end |
|---|
| 247 | if not context.headers["cache-control"] then |
|---|
| 248 | header("Cache-Control", "no-cache") |
|---|
| 249 | header("Expires", "0") |
|---|
| 250 | end |
|---|
| 251 | |
|---|
| 252 | |
|---|
| 253 | context.eoh = true |
|---|
| 254 | coroutine.yield(3) |
|---|
| 255 | end |
|---|
| 256 | coroutine.yield(4, content) |
|---|
| 257 | return true |
|---|
| 258 | end |
|---|
| 259 | end |
|---|
| 260 | |
|---|
| 261 | --- Splice data from a filedescriptor to the client. |
|---|
| 262 | -- @param fp File descriptor |
|---|
| 263 | -- @param size Bytes to splice (optional) |
|---|
| 264 | function splice(fd, size) |
|---|
| 265 | coroutine.yield(6, fd, size) |
|---|
| 266 | end |
|---|
| 267 | |
|---|
| 268 | --- Redirects the client to a new URL and closes the connection. |
|---|
| 269 | -- @param url Target URL |
|---|
| 270 | function redirect(url) |
|---|
| 271 | status(302, "Found") |
|---|
| 272 | header("Location", url) |
|---|
| 273 | close() |
|---|
| 274 | end |
|---|
| 275 | |
|---|
| 276 | --- Create a querystring out of a table of key - value pairs. |
|---|
| 277 | -- @param table Query string source table |
|---|
| 278 | -- @return Encoded HTTP query string |
|---|
| 279 | function build_querystring(table) |
|---|
| 280 | local s="?" |
|---|
| 281 | |
|---|
| 282 | for k, v in pairs(table) do |
|---|
| 283 | s = s .. urlencode(k) .. "=" .. urlencode(v) .. "&" |
|---|
| 284 | end |
|---|
| 285 | |
|---|
| 286 | return s |
|---|
| 287 | end |
|---|
| 288 | |
|---|
| 289 | --- Return the URL-decoded equivalent of a string. |
|---|
| 290 | -- @param str URL-encoded string |
|---|
| 291 | -- @param no_plus Don't decode + to " " |
|---|
| 292 | -- @return URL-decoded string |
|---|
| 293 | -- @see urlencode |
|---|
| 294 | urldecode = protocol.urldecode |
|---|
| 295 | |
|---|
| 296 | --- Return the URL-encoded equivalent of a string. |
|---|
| 297 | -- @param str Source string |
|---|
| 298 | -- @return URL-encoded string |
|---|
| 299 | -- @see urldecode |
|---|
| 300 | urlencode = protocol.urlencode |
|---|