luci-0.11: merge outstanding trunk changes
[project/luci.git] / libs / web / luasrc / http.lua
index b1ffac2bef4efba55d16b08e6c464316f89feda7..c53307a5a1eb4e2b3d72fabc6f2e0379c833eabd 100644 (file)
@@ -4,17 +4,14 @@ LuCI - HTTP-Interaction
 Description:
 HTTP-Header manipulator and form variable preprocessor
 
 Description:
 HTTP-Header manipulator and form variable preprocessor
 
-FileId:
-$Id$
-
 License:
 Copyright 2008 Steven Barth <steven@midlink.org>
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 License:
 Copyright 2008 Steven Barth <steven@midlink.org>
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
-You may obtain a copy of the License at 
+You may obtain a copy of the License at
 
 
-       http://www.apache.org/licenses/LICENSE-2.0 
+       http://www.apache.org/licenses/LICENSE-2.0
 
 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 
 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
@@ -24,15 +21,22 @@ limitations under the License.
 
 ]]--
 
 
 ]]--
 
+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
+
 --- LuCI Web Framework high-level HTTP functions.
 --- LuCI Web Framework high-level HTTP functions.
-module("luci.http", package.seeall)
-local ltn12 = require("luci.ltn12")
-require("luci.http.protocol")
-require("luci.util")
+module "luci.http"
 
 
-context = luci.util.threadlocal()
+context = util.threadlocal()
 
 
-Request = luci.util.class()
+Request = util.class()
 function Request.__init__(self, env, sourcein, sinkerr)
        self.input = sourcein
        self.error = sinkerr
 function Request.__init__(self, env, sourcein, sinkerr)
        self.input = sourcein
        self.error = sinkerr
@@ -40,14 +44,14 @@ function Request.__init__(self, env, sourcein, sinkerr)
 
        -- File handler
        self.filehandler = function() end
 
        -- File handler
        self.filehandler = function() end
-       
+
        -- HTTP-Message table
        self.message = {
                env = env,
                headers = {},
        -- HTTP-Message table
        self.message = {
                env = env,
                headers = {},
-               params = luci.http.protocol.urldecode_params(env.QUERY_STRING or ""),
+               params = protocol.urldecode_params(env.QUERY_STRING or ""),
        }
        }
-       
+
        self.parsed_input = false
 end
 
        self.parsed_input = false
 end
 
@@ -55,7 +59,7 @@ function Request.formvalue(self, name, noparse)
        if not noparse and not self.parsed_input then
                self:_parse_input()
        end
        if not noparse and not self.parsed_input then
                self:_parse_input()
        end
-       
+
        if name then
                return self.message.params[name]
        else
        if name then
                return self.message.params[name]
        else
@@ -66,18 +70,18 @@ end
 function Request.formvaluetable(self, prefix)
        local vals = {}
        prefix = prefix and prefix .. "." or "."
 function Request.formvaluetable(self, prefix)
        local vals = {}
        prefix = prefix and prefix .. "." or "."
-       
+
        if not self.parsed_input then
                self:_parse_input()
        end
        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
        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
 
        return vals
 end
 
@@ -85,7 +89,7 @@ function Request.content(self)
        if not self.parsed_input then
                self:_parse_input()
        end
        if not self.parsed_input then
                self:_parse_input()
        end
-       
+
        return self.message.content, self.message.content_length
 end
 
        return self.message.content, self.message.content_length
 end
 
@@ -109,7 +113,7 @@ function Request.setfilehandler(self, callback)
 end
 
 function Request._parse_input(self)
 end
 
 function Request._parse_input(self)
-       luci.http.protocol.parse_message_body(
+       protocol.parse_message_body(
                 self.input,
                 self.message,
                 self.filehandler
                 self.input,
                 self.message,
                 self.filehandler
@@ -123,7 +127,7 @@ function close()
                context.eoh = true
                coroutine.yield(3)
        end
                context.eoh = true
                coroutine.yield(3)
        end
-       
+
        if not context.closed then
                context.closed = true
                coroutine.yield(5)
        if not context.closed then
                context.closed = true
                coroutine.yield(5)
@@ -159,7 +163,7 @@ function getcookie(name)
        return context.request:getcookie(name)
 end
 
        return context.request:getcookie(name)
 end
 
---- Get the value of a certain HTTP environment variable 
+--- Get the value of a certain HTTP environment variable
 -- or the environment table itself.
 -- @param name         Environment variable
 -- @return                     HTTP environment value or environment table
 -- or the environment table itself.
 -- @param name         Environment variable
 -- @return                     HTTP environment value or environment table
@@ -187,14 +191,16 @@ end
 --- Set the mime type of following content data.
 -- @param mime Mimetype of following content
 function prepare_content(mime)
 --- Set the mime type of following content data.
 -- @param mime Mimetype of following content
 function prepare_content(mime)
-       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"
+       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
                end
-               header("Vary", "Accept")
+               header("Content-Type", mime)
        end
        end
-       header("Content-Type", mime)
 end
 
 --- Get the RAW HTTP input source
 end
 
 --- Get the RAW HTTP input source
@@ -241,8 +247,8 @@ function write(content, src_err)
                                header("Cache-Control", "no-cache")
                                header("Expires", "0")
                        end
                                header("Cache-Control", "no-cache")
                                header("Expires", "0")
                        end
-                       
-                       
+
+
                        context.eoh = true
                        coroutine.yield(3)
                end
                        context.eoh = true
                        coroutine.yield(3)
                end
@@ -251,6 +257,13 @@ function write(content, src_err)
        end
 end
 
        end
 end
 
+--- Splice data from a filedescriptor to the client.
+-- @param fp   File descriptor
+-- @param size Bytes to splice (optional)
+function splice(fd, size)
+       coroutine.yield(6, fd, size)
+end
+
 --- Redirects the client to a new URL and closes the connection.
 -- @param url  Target URL
 function redirect(url)
 --- Redirects the client to a new URL and closes the connection.
 -- @param url  Target URL
 function redirect(url)
@@ -262,14 +275,18 @@ end
 --- Create a querystring out of a table of key - value pairs.
 -- @param table                Query string source table
 -- @return                     Encoded HTTP query string
 --- Create a querystring out of a table of key - value pairs.
 -- @param table                Query string source table
 -- @return                     Encoded HTTP query string
-function build_querystring(table)
-       local s="?"
-       
-       for k, v in pairs(table) do
-               s = s .. urlencode(k) .. "=" .. urlencode(v) .. "&"
+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
        end
-       
-       return s
+
+       return table.concat(s, "")
 end
 
 --- Return the URL-decoded equivalent of a string.
 end
 
 --- Return the URL-decoded equivalent of a string.
@@ -277,10 +294,51 @@ end
 -- @param no_plus      Don't decode + to " "
 -- @return                     URL-decoded string
 -- @see urlencode
 -- @param no_plus      Don't decode + to " "
 -- @return                     URL-decoded string
 -- @see urlencode
-urldecode = luci.http.protocol.urldecode
+urldecode = protocol.urldecode
 
 --- Return the URL-encoded equivalent of a string.
 -- @param str          Source string
 -- @return                     URL-encoded string
 -- @see urldecode
 
 --- Return the URL-encoded equivalent of a string.
 -- @param str          Source string
 -- @return                     URL-encoded string
 -- @see urldecode
-urlencode = luci.http.protocol.urlencode
+urlencode = protocol.urlencode
+
+--- Send the given data as JSON encoded string.
+-- @param data         Data to send
+function write_json(x)
+       if x == nil then
+               write("null")
+       elseif type(x) == "table" then
+               local k, v
+               if type(next(x)) == "number" then
+                       write("[ ")
+                       for k, v in ipairs(x) do
+                               write_json(v)
+                               if next(x, k) then
+                                       write(", ")
+                               end
+                       end
+                       write(" ]")
+               else
+                       write("{ ")
+                       for k, v in pairs(x) do
+                       write("%q: " % k)
+                               write_json(v)
+                               if next(x, k) then
+                                       write(", ")
+                               end
+                       end
+                       write(" }")
+               end
+       elseif type(x) == "number" or type(x) == "boolean" then
+               if (x ~= x) then
+                       -- NaN is the only value that doesn't equal to itself.
+                       write("Number.NaN")
+               else
+                       write(tostring(x))
+               end
+       else
+               write('"%s"' % tostring(x):gsub('["%z\1-\31]', function(c)
+                       return '\\u%04x' % c:byte(1)
+               end))
+       end
+end