123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276 |
- -- Copyright 2008 Steven Barth <steven@midlink.org>
- -- Licensed to the public under the Apache License 2.0.
- local ltn12 = require "luci.ltn12"
- local protocol = require "luci.http.protocol"
- local util = require "luci.util"
- local string = require "string"
- local coroutine = require "coroutine"
- local table = require "table"
- local ipairs, pairs, next, type, tostring, error =
- ipairs, pairs, next, type, tostring, error
- module "luci.http"
- context = util.threadlocal()
- Request = util.class()
- function Request.__init__(self, env, sourcein, sinkerr)
- self.input = sourcein
- self.error = sinkerr
- -- File handler nil by default to let .content() work
- self.filehandler = nil
- -- HTTP-Message table
- self.message = {
- env = env,
- headers = {},
- params = protocol.urldecode_params(env.QUERY_STRING or ""),
- }
- self.parsed_input = false
- end
- function Request.formvalue(self, name, noparse)
- if not noparse and not self.parsed_input then
- self:_parse_input()
- end
- if name then
- return self.message.params[name]
- else
- return self.message.params
- end
- end
- function Request.formvaluetable(self, prefix)
- local vals = {}
- prefix = prefix and prefix .. "." or "."
- if not self.parsed_input then
- self:_parse_input()
- end
- local void = self.message.params[nil]
- for k, v in pairs(self.message.params) do
- if k:find(prefix, 1, true) == 1 then
- vals[k:sub(#prefix + 1)] = tostring(v)
- end
- end
- return vals
- end
- function Request.content(self)
- if not self.parsed_input then
- self:_parse_input()
- end
- return self.message.content, self.message.content_length
- end
- function Request.getcookie(self, name)
- local c = string.gsub(";" .. (self:getenv("HTTP_COOKIE") or "") .. ";", "%s*;%s*", ";")
- local p = ";" .. name .. "=(.-);"
- local i, j, value = c:find(p)
- return value and urldecode(value)
- end
- function Request.getenv(self, name)
- if name then
- return self.message.env[name]
- else
- return self.message.env
- end
- end
- function Request.setfilehandler(self, callback)
- self.filehandler = callback
- -- If input has already been parsed then any files are either in temporary files
- -- or are in self.message.params[key]
- if self.parsed_input then
- for param, value in pairs(self.message.params) do
- repeat
- -- We're only interested in files
- if (not value["file"]) then break end
- -- If we were able to write to temporary file
- if (value["fd"]) then
- fd = value["fd"]
- local eof = false
- repeat
- filedata = fd:read(1024)
- if (filedata:len() < 1024) then
- eof = true
- end
- callback({ name=value["name"], file=value["file"] }, filedata, eof)
- until (eof)
- fd:close()
- value["fd"] = nil
- -- We had to read into memory
- else
- -- There should only be one numbered value in table - the data
- for k, v in ipairs(value) do
- callback({ name=value["name"], file=value["file"] }, v, true)
- end
- end
- until true
- end
- end
- end
- function Request._parse_input(self)
- protocol.parse_message_body(
- self.input,
- self.message,
- self.filehandler
- )
- self.parsed_input = true
- end
- function close()
- if not context.eoh then
- context.eoh = true
- coroutine.yield(3)
- end
- if not context.closed then
- context.closed = true
- coroutine.yield(5)
- end
- end
- function content()
- return context.request:content()
- end
- function formvalue(name, noparse)
- return context.request:formvalue(name, noparse)
- end
- function formvaluetable(prefix)
- return context.request:formvaluetable(prefix)
- end
- function getcookie(name)
- return context.request:getcookie(name)
- end
- -- or the environment table itself.
- function getenv(name)
- return context.request:getenv(name)
- end
- function setfilehandler(callback)
- return context.request:setfilehandler(callback)
- end
- function header(key, value)
- if not context.headers then
- context.headers = {}
- end
- context.headers[key:lower()] = value
- coroutine.yield(2, key, value)
- end
- function prepare_content(mime)
- if not context.headers or not context.headers["content-type"] then
- if mime == "application/xhtml+xml" then
- if not getenv("HTTP_ACCEPT") or
- not getenv("HTTP_ACCEPT"):find("application/xhtml+xml", nil, true) then
- mime = "text/html; charset=UTF-8"
- end
- header("Vary", "Accept")
- end
- header("Content-Type", mime)
- end
- end
- function source()
- return context.request.input
- end
- function status(code, message)
- code = code or 200
- message = message or "OK"
- context.status = code
- coroutine.yield(1, code, message)
- end
- -- This function is as a valid LTN12 sink.
- -- If the content chunk is nil this function will automatically invoke close.
- function write(content, src_err)
- if not content then
- if src_err then
- error(src_err)
- else
- close()
- end
- return true
- elseif #content == 0 then
- return true
- else
- if not context.eoh then
- if not context.status then
- status()
- end
- if not context.headers or not context.headers["content-type"] then
- header("Content-Type", "text/html; charset=utf-8")
- end
- if not context.headers["cache-control"] then
- header("Cache-Control", "no-cache")
- header("Expires", "0")
- end
- if not context.headers["x-frame-options"] then
- header("X-Frame-Options", "SAMEORIGIN")
- end
- if not context.headers["x-xss-protection"] then
- header("X-XSS-Protection", "1; mode=block")
- end
- if not context.headers["x-content-type-options"] then
- header("X-Content-Type-Options", "nosniff")
- end
- context.eoh = true
- coroutine.yield(3)
- end
- coroutine.yield(4, content)
- return true
- end
- end
- function splice(fd, size)
- coroutine.yield(6, fd, size)
- end
- function redirect(url)
- if url == "" then url = "/" end
- status(302, "Found")
- header("Location", url)
- close()
- end
- function build_querystring(q)
- local s = { "?" }
- for k, v in pairs(q) do
- if #s > 1 then s[#s+1] = "&" end
- s[#s+1] = urldecode(k)
- s[#s+1] = "="
- s[#s+1] = urldecode(v)
- end
- return table.concat(s, "")
- end
- urldecode = protocol.urldecode
- urlencode = protocol.urlencode
- function write_json(x)
- util.serialize_json(x, write)
- end
|