summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--server/src/3rd/lua-uri/uri.lua504
-rw-r--r--server/src/3rd/lua-uri/uri/_login.lua96
-rw-r--r--server/src/3rd/lua-uri/uri/_relative.lua81
-rw-r--r--server/src/3rd/lua-uri/uri/_util.lua128
-rw-r--r--server/src/3rd/lua-uri/uri/data.lua116
-rw-r--r--server/src/3rd/lua-uri/uri/file.lua72
-rw-r--r--server/src/3rd/lua-uri/uri/file/unix.lua26
-rw-r--r--server/src/3rd/lua-uri/uri/file/win32.lua34
-rw-r--r--server/src/3rd/lua-uri/uri/ftp.lua53
-rw-r--r--server/src/3rd/lua-uri/uri/http.lua32
-rw-r--r--server/src/3rd/lua-uri/uri/https.lua9
-rw-r--r--server/src/3rd/lua-uri/uri/pop.lua111
-rw-r--r--server/src/3rd/lua-uri/uri/rtsp.lua9
-rw-r--r--server/src/3rd/lua-uri/uri/rtspu.lua7
-rw-r--r--server/src/3rd/lua-uri/uri/telnet.lua38
-rw-r--r--server/src/3rd/lua-uri/uri/urn.lua131
-rw-r--r--server/src/3rd/lua-uri/uri/urn/isbn.lua67
-rw-r--r--server/src/3rd/lua-uri/uri/urn/issn.lua65
-rw-r--r--server/src/3rd/lua-uri/uri/urn/oid.lua62
-rw-r--r--server/src/sandbox.lua163
-rw-r--r--server/src/uri.lua61
-rw-r--r--server/src/workspace.lua1
22 files changed, 1818 insertions, 48 deletions
diff --git a/server/src/3rd/lua-uri/uri.lua b/server/src/3rd/lua-uri/uri.lua
new file mode 100644
index 00000000..395edcd9
--- /dev/null
+++ b/server/src/3rd/lua-uri/uri.lua
@@ -0,0 +1,504 @@
+local M = { _NAME = "uri", VERSION = "1.0" }
+M.__index = M
+
+local Util = require "uri._util"
+
+local _UNRESERVED = "A-Za-z0-9%-._~"
+local _GEN_DELIMS = ":/?#%[%]@"
+local _SUB_DELIMS = "!$&'()*+,;="
+local _RESERVED = _GEN_DELIMS .. _SUB_DELIMS
+local _USERINFO = "^[" .. _UNRESERVED .. "%%" .. _SUB_DELIMS .. ":]*$"
+local _REG_NAME = "^[" .. _UNRESERVED .. "%%" .. _SUB_DELIMS .. "]*$"
+local _IP_FUTURE_LITERAL = "^v[0-9A-Fa-f]+%." ..
+ "[" .. _UNRESERVED .. _SUB_DELIMS .. "]+$"
+local _QUERY_OR_FRAG = "^[" .. _UNRESERVED .. "%%" .. _SUB_DELIMS .. ":@/?]*$"
+local _PATH_CHARS = "^[" .. _UNRESERVED .. "%%" .. _SUB_DELIMS .. ":@/]*$"
+
+local function _normalize_percent_encoding (s)
+ if s:find("%%$") or s:find("%%.$") then
+ error("unfinished percent encoding at end of URI '" .. s .. "'")
+ end
+
+ return s:gsub("%%(..)", function (hex)
+ if not hex:find("^[0-9A-Fa-f][0-9A-Fa-f]$") then
+ error("invalid percent encoding '%" .. hex ..
+ "' in URI '" .. s .. "'")
+ end
+
+ -- Never percent-encode unreserved characters, and always use uppercase
+ -- hexadecimal for percent encoding. RFC 3986 section 6.2.2.2.
+ local char = string.char(tonumber("0x" .. hex))
+ return char:find("^[" .. _UNRESERVED .. "]") and char or "%" .. hex:upper()
+ end)
+end
+
+local function _is_ip4_literal (s)
+ if not s:find("^[0-9]+%.[0-9]+%.[0-9]+%.[0-9]+$") then return false end
+
+ for dec_octet in s:gmatch("[0-9]+") do
+ if dec_octet:len() > 3 or dec_octet:find("^0.") or
+ tonumber(dec_octet) > 255 then
+ return false
+ end
+ end
+
+ return true
+end
+
+local function _is_ip6_literal (s)
+ local had_elipsis = false -- true when '::' found
+ local num_chunks = 0
+ while s ~= "" do
+ num_chunks = num_chunks + 1
+ local p1, p2 = s:find("::?")
+ local chunk
+ if p1 then
+ chunk = s:sub(1, p1 - 1)
+ s = s:sub(p2 + 1)
+ if p2 ~= p1 then -- found '::'
+ if had_elipsis then return false end -- two of '::'
+ had_elipsis = true
+ if chunk == "" then num_chunks = num_chunks - 1 end
+ else
+ if chunk == "" then return false end -- ':' at start
+ if s == "" then return false end -- ':' at end
+ end
+ else
+ chunk = s
+ s = ""
+ end
+
+ -- Chunk is neither 4-digit hex num, nor IPv4address in last chunk.
+ if (not chunk:find("^[0-9a-f]+$") or chunk:len() > 4) and
+ (s ~= "" or not _is_ip4_literal(chunk)) and
+ chunk ~= "" then
+ return false
+ end
+
+ -- IPv4address in last position counts for two chunks of hex digits.
+ if chunk:len() > 4 then num_chunks = num_chunks + 1 end
+ end
+
+ if had_elipsis then
+ if num_chunks > 7 then return false end
+ else
+ if num_chunks ~= 8 then return false end
+ end
+
+ return true
+end
+
+local function _is_valid_host (host)
+ if host:find("^%[.*%]$") then
+ local ip_literal = host:sub(2, -2)
+ if ip_literal:find("^v") then
+ if not ip_literal:find(_IP_FUTURE_LITERAL) then
+ return "invalid IPvFuture literal '" .. ip_literal .. "'"
+ end
+ else
+ if not _is_ip6_literal(ip_literal) then
+ return "invalid IPv6 address '" .. ip_literal .. "'"
+ end
+ end
+ elseif not _is_ip4_literal(host) and not host:find(_REG_NAME) then
+ return "invalid host value '" .. host .. "'"
+ end
+
+ return nil
+end
+
+local function _normalize_and_check_path (s, normalize)
+ if not s:find(_PATH_CHARS) then return false end
+ if not normalize then return s end
+
+ -- Remove unnecessary percent encoding for path values.
+ -- TODO - I think this should be HTTP-specific (probably file also).
+ --s = Util.uri_decode(s, _SUB_DELIMS .. ":@")
+
+ return Util.remove_dot_segments(s)
+end
+
+function M.new (class, uri, base)
+ if not uri then error("usage: URI:new(uristring, [baseuri])") end
+ if type(uri) ~= "string" then uri = tostring(uri) end
+
+ if base then
+ local uri, err = M.new(class, uri)
+ if not uri then return nil, err end
+ if type(base) ~= "table" then
+ base, err = M.new(class, base)
+ if not base then return nil, "error parsing base URI: " .. err end
+ end
+ if base:is_relative() then return nil, "base URI must be absolute" end
+ local ok, err = pcall(uri.resolve, uri, base)
+ if not ok then return nil, err end
+ return uri
+ end
+
+ local s = _normalize_percent_encoding(uri)
+
+ local _, p
+ local scheme, authority, userinfo, host, port, path, query, fragment
+
+ _, p, scheme = s:find("^([a-zA-Z][-+.a-zA-Z0-9]*):")
+ if scheme then
+ scheme = scheme:lower()
+ s = s:sub(p + 1)
+ end
+
+ _, p, authority = s:find("^//([^/?#]*)")
+ if authority then
+ s = s:sub(p + 1)
+
+ _, p, userinfo = authority:find("^([^@]*)@")
+ if userinfo then
+ if not userinfo:find(_USERINFO) then
+ return nil, "invalid userinfo value '" .. userinfo .. "'"
+ end
+ authority = authority:sub(p + 1)
+ end
+
+ p, _, port = authority:find(":([0-9]*)$")
+ if port then
+ port = (port ~= "") and tonumber(port) or nil
+ authority = authority:sub(1, p - 1)
+ end
+
+ host = authority:lower()
+ local err = _is_valid_host(host)
+ if err then return nil, err end
+ end
+
+ _, p, path = s:find("^([^?#]*)")
+ if path ~= "" then
+ local normpath = _normalize_and_check_path(path, scheme)
+ if not normpath then return nil, "invalid path '" .. path .. "'" end
+ path = normpath
+ s = s:sub(p + 1)
+ end
+
+ _, p, query = s:find("^%?([^#]*)")
+ if query then
+ s = s:sub(p + 1)
+ if not query:find(_QUERY_OR_FRAG) then
+ return nil, "invalid query value '?" .. query .. "'"
+ end
+ end
+
+ _, p, fragment = s:find("^#(.*)")
+ if fragment then
+ if not fragment:find(_QUERY_OR_FRAG) then
+ return nil, "invalid fragment value '#" .. fragment .. "'"
+ end
+ end
+
+ local o = {
+ _scheme = scheme,
+ _userinfo = userinfo,
+ _host = host,
+ _port = port,
+ _path = path,
+ _query = query,
+ _fragment = fragment,
+ }
+ setmetatable(o, scheme and class or (require "uri._relative"))
+
+ return o:init()
+end
+
+function M.uri (self, ...)
+ local uri = self._uri
+
+ if not uri then
+ local scheme = self:scheme()
+ if scheme then
+ uri = scheme .. ":"
+ else
+ uri = ""
+ end
+
+ local host, port, userinfo = self:host(), self._port, self:userinfo()
+ if host or port or userinfo then
+ uri = uri .. "//"
+ if userinfo then uri = uri .. userinfo .. "@" end
+ if host then uri = uri .. host end
+ if port then uri = uri .. ":" .. port end
+ end
+
+ local path = self:path()
+ if uri == "" and path:find("^[^/]*:") then
+ path = "./" .. path
+ end
+
+ uri = uri .. path
+ if self:query() then uri = uri .. "?" .. self:query() end
+ if self:fragment() then uri = uri .. "#" .. self:fragment() end
+
+ self._uri = uri -- cache
+ end
+
+ if select("#", ...) > 0 then
+ local new = ...
+ if not new then error("URI can't be set to nil") end
+ local newuri, err = M:new(new)
+ if not newuri then
+ error("new URI string is invalid (" .. err .. ")")
+ end
+ setmetatable(self, getmetatable(newuri))
+ for k in pairs(self) do self[k] = nil end
+ for k, v in pairs(newuri) do self[k] = v end
+ end
+
+ return uri
+end
+
+function M.__tostring (self) return self:uri() end
+
+function M.eq (a, b)
+ if type(a) == "string" then a = assert(M:new(a)) end
+ if type(b) == "string" then b = assert(M:new(b)) end
+ return a:uri() == b:uri()
+end
+
+function M.scheme (self, ...)
+ local old = self._scheme
+
+ if select("#", ...) > 0 then
+ local new = ...
+ if not new then error("can't remove scheme from absolute URI") end
+ if type(new) ~= "string" then new = tostring(new) end
+ if not new:find("^[a-zA-Z][-+.a-zA-Z0-9]*$") then
+ error("invalid scheme '" .. new .. "'")
+ end
+ Util.do_class_changing_change(self, M, "scheme", new,
+ function (uri, new) uri._scheme = new end)
+ end
+
+ return old
+end
+
+function M.userinfo (self, ...)
+ local old = self._userinfo
+
+ if select("#", ...) > 0 then
+ local new = ...
+ if new then
+ if not new:find(_USERINFO) then
+ error("invalid userinfo value '" .. new .. "'")
+ end
+ new = _normalize_percent_encoding(new)
+ end
+ self._userinfo = new
+ if new and not self._host then self._host = "" end
+ self._uri = nil
+ end
+
+ return old
+end
+
+function M.host (self, ...)
+ local old = self._host
+
+ if select("#", ...) > 0 then
+ local new = ...
+ if new then
+ new = tostring(new):lower()
+ local err = _is_valid_host(new)
+ if err then error(err) end
+ else
+ if self._userinfo or self._port then
+ error("there must be a host if there is a userinfo or port," ..
+ " although it can be the empty string")
+ end
+ end
+ self._host = new
+ self._uri = nil
+ end
+
+ return old
+end
+
+function M.port (self, ...)
+ local old = self._port or self:default_port()
+
+ if select("#", ...) > 0 then
+ local new = ...
+ if new then
+ if type(new) == "string" then new = tonumber(new) end
+ if new < 0 then error("port number must not be negative") end
+ local newint = new - new % 1
+ if newint ~= new then error("port number not integer") end
+ if new == self:default_port() then new = nil end
+ end
+ self._port = new
+ if new and not self._host then self._host = "" end
+ self._uri = nil
+ end
+
+ return old
+end
+
+function M.path (self, ...)
+ local old = self._path
+
+ if select("#", ...) > 0 then
+ local new = ... or ""
+ new = _normalize_percent_encoding(new)
+ new = Util.uri_encode(new, "^A-Za-z0-9%-._~%%!$&'()*+,;=:@/")
+ if self._host then
+ if new ~= "" and not new:find("^/") then
+ error("path must begin with '/' when there is an authority")
+ end
+ else
+ if new:find("^//") then new = "/%2F" .. new:sub(3) end
+ end
+ self._path = new
+ self._uri = nil
+ end
+
+ return old
+end
+
+function M.query (self, ...)
+ local old = self._query
+
+ if select("#", ...) > 0 then
+ local new = ...
+ if new then
+ new = Util.uri_encode(new, "^" .. _UNRESERVED .. "%%" .. _SUB_DELIMS .. ":@/?")
+ end
+ self._query = new
+ self._uri = nil
+ end
+
+ return old
+end
+
+function M.fragment (self, ...)
+ local old = self._fragment
+
+ if select("#", ...) > 0 then
+ local new = ...
+ if new then
+ new = Util.uri_encode(new, "^" .. _UNRESERVED .. "%%" .. _SUB_DELIMS .. ":@/?")
+ end
+ self._fragment = new
+ self._uri = nil
+ end
+
+ return old
+end
+
+function M.init (self)
+ local scheme_class
+ = Util.attempt_require("uri." .. self._scheme:gsub("[-+.]", "_"))
+ if scheme_class then
+ setmetatable(self, scheme_class)
+ if self._port and self._port == self:default_port() then
+ self._port = nil
+ end
+ -- Call the subclass 'init' method, if it has its own.
+ if scheme_class ~= M and self.init ~= M.init then
+ return self:init()
+ end
+ end
+ return self
+end
+
+function M.default_port () return nil end
+function M.is_relative () return false end
+function M.resolve () end -- only does anything in uri._relative
+
+-- TODO - there should probably be an option or something allowing you to
+-- choose between making a link relative whenever possible (always using a
+-- relative path if the scheme and authority are the same as the base URI) or
+-- just using a relative reference to make the link as small as possible, which
+-- might meaning using a path of '/' instead if '../../../' or whatever.
+-- This method's algorithm is loosely based on the one described here:
+-- http://lists.w3.org/Archives/Public/uri/2007Sep/0003.html
+function M.relativize (self, base)
+ if type(base) == "string" then base = assert(M:new(base)) end
+
+ -- Leave it alone if we can't a relative URI, or if it would be a network
+ -- path reference.
+ if self._scheme ~= base._scheme or self._host ~= base._host or
+ self._port ~= base._port or self._userinfo ~= base._userinfo then
+ return
+ end
+
+ local basepath = base._path
+ local oldpath = self._path
+ -- This is to avoid trying to make a URN or something relative, which
+ -- is likely to lead to grief.
+ if not basepath:find("^/") or not oldpath:find("^/") then return end
+
+ -- Turn it into a relative reference.
+ self._uri = nil
+ self._scheme = nil
+ self._host = nil
+ self._port = nil
+ self._userinfo = nil
+ setmetatable(self, require "uri._relative")
+
+ -- Use empty path if the path in the base URI is already correct.
+ if oldpath == basepath then
+ if self._query or not base._query then
+ self._path = ""
+ else
+ -- An empty URI reference leaves the query string in the base URI
+ -- unchanged, so to get a result with no query part we have to
+ -- have something in the relative path.
+ local _, _, lastseg = oldpath:find("/([^/]+)$")
+ if lastseg and lastseg:find(":") then lastseg = "./" .. lastseg end
+ self._path = lastseg or "."
+ end
+ return
+ end
+
+ if oldpath == "/" or basepath == "/" then return end
+
+ local basesegs = Util.split("/", basepath:sub(2))
+ local oldsegs = Util.split("/", oldpath:sub(2))
+
+ if oldsegs[1] ~= basesegs[1] then return end
+
+ table.remove(basesegs)
+
+ while #oldsegs > 1 and #basesegs > 0 and oldsegs[1] == basesegs[1] do
+ table.remove(oldsegs, 1)
+ table.remove(basesegs, 1)
+ end
+
+ local path_naked = true
+ local newpath = ""
+ while #basesegs > 0 do
+ table.remove(basesegs, 1)
+ newpath = newpath .. "../"
+ path_naked = false
+ end
+
+ if path_naked and #oldsegs == 1 and oldsegs[1] == "" then
+ newpath = "./"
+ table.remove(oldsegs)
+ end
+
+ while #oldsegs > 0 do
+ if path_naked then
+ if oldsegs[1]:find(":") then
+ newpath = newpath .. "./"
+ elseif #oldsegs > 1 and oldsegs[1] == "" and oldsegs[2] == "" then
+ newpath = newpath .. "/."
+ end
+ end
+
+ newpath = newpath .. oldsegs[1]
+ path_naked = false
+ table.remove(oldsegs, 1)
+ if #oldsegs > 0 then newpath = newpath .. "/" end
+ end
+
+ self._path = newpath
+end
+
+return M
+-- vi:ts=4 sw=4 expandtab
diff --git a/server/src/3rd/lua-uri/uri/_login.lua b/server/src/3rd/lua-uri/uri/_login.lua
new file mode 100644
index 00000000..4e9e6844
--- /dev/null
+++ b/server/src/3rd/lua-uri/uri/_login.lua
@@ -0,0 +1,96 @@
+local M = { _NAME = "uri._login" }
+local Util = require "uri._util"
+local URI = require "uri"
+Util.subclass_of(M, URI)
+
+-- Generic terminal logins. This is used as a base class for 'telnet' and
+-- 'ftp' URL schemes.
+
+local function _valid_userinfo (userinfo)
+ if userinfo then
+ local colon = userinfo:find(":")
+ if colon and userinfo:find(":", colon + 1) then
+ return nil, "only one colon allowed in userinfo"
+ end
+ end
+ return true
+end
+
+-- TODO - this is a bit of a hack because currently subclasses are required
+-- to know whether their superclass has one of these that needs calling.
+-- It should be called from 'init' before anything more specific is done,
+-- and it has the same calling convention.
+-- According to RFC 1738 there should be at most one colon in the userinfo.
+-- I apply that restriction for schemes where it's used for a username/password
+-- pair.
+function M.init_base (self)
+ local host = self:host()
+ if not host or host == "" then
+ return nil, "host missing from login URI"
+ end
+
+ local ok, err = _valid_userinfo(self:userinfo())
+ if not ok then return nil, err end
+
+ return self
+end
+
+function M.userinfo (self, ...)
+ if select("#", ...) > 0 then
+ local ok, err = _valid_userinfo(...)
+ if not ok then error("invalid userinfo value (" .. err .. ")") end
+ end
+ return M._SUPER.userinfo(self, ...)
+end
+
+function M.username (self, ...)
+ local info = M._SUPER.userinfo(self)
+ local old, colon
+ if info then
+ local colon = info and info:find(":")
+ old = colon and info:sub(1, colon - 1) or info
+ old = Util.uri_decode(old)
+ end
+
+ if select('#', ...) > 0 then
+ local pass = colon and info:sub(colon) or "" -- includes colon
+ local new = ...
+ if not new then
+ M._SUPER.userinfo(self, nil)
+ else
+ -- Escape anything that's not allowed in a userinfo, and also
+ -- colon, because that indicates the end of the username.
+ new = Util.uri_encode(new, "^A-Za-z0-9%-._~!$&'()*+,;=")
+ M._SUPER.userinfo(self, new .. pass)
+ end
+ end
+
+ return old
+end
+
+function M.password (self, ...)
+ local info = M._SUPER.userinfo(self)
+ local old, colon
+ if info then
+ colon = info and info:find(":")
+ old = colon and info:sub(colon + 1) or nil
+ if old then old = Util.uri_decode(old) end
+ end
+
+ if select('#', ...) > 0 then
+ local new = ...
+ local user = colon and info:sub(1, colon - 1) or info
+ if not new then
+ M._SUPER.userinfo(self, user)
+ else
+ if not user then user = "" end
+ new = Util.uri_encode(new, "^A-Za-z0-9%-._~!$&'()*+,;=")
+ M._SUPER.userinfo(self, user .. ":" .. new)
+ end
+ end
+
+ return old
+end
+
+return M
+-- vi:ts=4 sw=4 expandtab
diff --git a/server/src/3rd/lua-uri/uri/_relative.lua b/server/src/3rd/lua-uri/uri/_relative.lua
new file mode 100644
index 00000000..8cd53ca7
--- /dev/null
+++ b/server/src/3rd/lua-uri/uri/_relative.lua
@@ -0,0 +1,81 @@
+local M = { _NAME = "uri._relative" }
+local Util = require "uri._util"
+local URI = require "uri"
+Util.subclass_of(M, URI)
+
+-- There needs to be an 'init' method in this class, to because the base-class
+-- one expects there to be a 'scheme' value.
+function M.init (self)
+ return self
+end
+
+function M.scheme (self, ...)
+ if select("#", ...) > 0 then
+ error("relative URI references can't have a scheme, perhaps you" ..
+ " need to resolve this against an absolute URI instead")
+ end
+ return nil
+end
+
+function M.is_relative () return true end
+
+-- This implements the algorithm from RFC 3986 section 5.2.3
+-- Note that this takes an additional argument which appears to be required
+-- by the algorithm, but isn't shown when it is used in the RFC.
+local function _merge_paths (base, r, base_has_auth)
+ if base_has_auth and base == "" then
+ return "/" .. r
+ end
+
+ return base:gsub("[^/]+$", "", 1) .. r
+end
+
+local function _do_resolve (self, base)
+ if type(base) == "string" then base = assert(URI:new(base)) end
+ setmetatable(self, URI)
+
+ if self:host() or self:userinfo() or self:port() then
+ -- network path reference, just needs a scheme
+ self:path(Util.remove_dot_segments(self:path()))
+ self:scheme(base:scheme())
+ return
+ end
+
+ local path = self:path()
+ if path == "" then
+ self:path(base:path())
+ if not self:query() then self:query(base:query()) end
+ else
+ if path:find("^/") then
+ self:path(Util.remove_dot_segments(path))
+ else
+ local base_has_auth = base:host() or base:userinfo() or base:port()
+ local merged = _merge_paths(base:path(), path, base_has_auth)
+ self:path(Util.remove_dot_segments(merged))
+ end
+ end
+ self:host(base:host())
+ self:userinfo(base:userinfo())
+ self:port(base:port())
+ self:scheme(base:scheme())
+end
+
+function M.resolve (self, base)
+ local orig = tostring(self)
+ local ok, result = pcall(_do_resolve, self, base)
+ if ok then return end
+
+ -- If the resolving causes an exception, it means that the resulting URI
+ -- would be invalid, so we restore self to its original state and rethrow
+ -- the exception.
+ local restored = assert(URI:new(orig))
+ for k in pairs(self) do self[k] = nil end
+ for k, v in pairs(restored) do self[k] = v end
+ setmetatable(self, getmetatable(restored))
+ error("resolved URI reference would be invalid: " .. result)
+end
+
+function M.relativize (self, base) end -- already relative
+
+return M
+-- vi:ts=4 sw=4 expandtab
diff --git a/server/src/3rd/lua-uri/uri/_util.lua b/server/src/3rd/lua-uri/uri/_util.lua
new file mode 100644
index 00000000..16a3b289
--- /dev/null
+++ b/server/src/3rd/lua-uri/uri/_util.lua
@@ -0,0 +1,128 @@
+local M = { _NAME = "uri._util" }
+
+-- Build a char->hex map
+local escapes = {}
+for i = 0, 255 do
+ escapes[string.char(i)] = string.format("%%%02X", i)
+end
+
+function M.uri_encode (text, patn)
+ if not text then return end
+ if not patn then
+ -- Default unsafe characters. RFC 2732 ^(uric - reserved)
+ -- TODO - this should be updated to the latest RFC.
+ patn = "^A-Za-z0-9%-_.!~*'()"
+ end
+ return (text:gsub("([" .. patn .. "])",
+ function (chr) return escapes[chr] end))
+end
+
+function M.uri_decode (str, patn)
+ -- Note from RFC1630: "Sequences which start with a percent sign
+ -- but are not followed by two hexadecimal characters are reserved
+ -- for future extension"
+ if not str then return end
+ if patn then patn = "[" .. patn .. "]" end
+ return (str:gsub("%%(%x%x)", function (hex)
+ local char = string.char(tonumber(hex, 16))
+ return (patn and not char:find(patn)) and "%" .. hex or char
+ end))
+end
+
+-- This is the remove_dot_segments algorithm from RFC 3986 section 5.2.4.
+-- The input buffer is 's', the output buffer 'path'.
+function M.remove_dot_segments (s)
+ local path = ""
+
+ while s ~= "" do
+ if s:find("^%.%.?/") then -- A
+ s = s:gsub("^%.%.?/", "", 1)
+ elseif s:find("^/%./") or s == "/." then -- B
+ s = s:gsub("^/%./?", "/", 1)
+ elseif s:find("^/%.%./") or s == "/.." then -- C
+ s = s:gsub("^/%.%./?", "/", 1)
+ if path:find("/") then
+ path = path:gsub("/[^/]*$", "", 1)
+ else
+ path = ""
+ end
+ elseif s == "." or s == ".." then -- D
+ s = ""
+ else -- E
+ local _, p, seg = s:find("^(/?[^/]*)")
+ s = s:sub(p + 1)
+ path = path .. seg
+ end
+ end
+
+ return path
+end
+
+-- TODO - wouldn't this be better as a method on string? s:split(patn)
+function M.split (patn, s, max)
+ if s == "" then return {} end
+
+ local i, j = 1, string.find(s, patn)
+ if not j then return { s } end
+
+ local list = {}
+ while true do
+ if #list + 1 == max then list[max] = s:sub(i); return list end
+ list[#list + 1] = s:sub(i, j - 1)
+ i = j + 1
+ j = string.find(s, patn, i)
+ if not j then
+ list[#list + 1] = s:sub(i)
+ break
+ end
+ end
+ return list
+end
+
+function M.attempt_require (modname)
+ local ok, result = pcall(require, modname)
+ if ok then
+ return result
+ elseif type(result) == "string" and
+ result:find("module '.*' not found") then
+ return nil
+ else
+ error(result)
+ end
+end
+
+function M.subclass_of (class, baseclass)
+ class.__index = class
+ class.__tostring = baseclass.__tostring
+ class._SUPER = baseclass
+ setmetatable(class, baseclass)
+end
+
+function M.do_class_changing_change (uri, baseclass, changedesc, newvalue,
+ changefunc)
+ local tmpuri = {}
+ setmetatable(tmpuri, baseclass)
+ for k, v in pairs(uri) do tmpuri[k] = v end
+ changefunc(tmpuri, newvalue)
+ tmpuri._uri = nil
+
+ local foo, err = tmpuri:init()
+ if not foo then
+ error("URI not valid after " .. changedesc .. " changed to '" ..
+ newvalue .. "': " .. err)
+ end
+
+ setmetatable(uri, getmetatable(tmpuri))
+ for k in pairs(uri) do uri[k] = nil end
+ for k, v in pairs(tmpuri) do uri[k] = v end
+end
+
+function M.uri_part_not_allowed (class, method)
+ class[method] = function (self, new)
+ if new then error(method .. " not allowed on this kind of URI") end
+ return self["_" .. method]
+ end
+end
+
+return M
+-- vi:ts=4 sw=4 expandtab
diff --git a/server/src/3rd/lua-uri/uri/data.lua b/server/src/3rd/lua-uri/uri/data.lua
new file mode 100644
index 00000000..c425621a
--- /dev/null
+++ b/server/src/3rd/lua-uri/uri/data.lua
@@ -0,0 +1,116 @@
+local M = { _NAME = "uri.data" }
+local Util = require "uri._util"
+local URI = require "uri"
+Util.subclass_of(M, URI)
+
+-- This implements the 'data' scheme defined in RFC 2397.
+
+local Filter = Util.attempt_require("datafilter")
+
+local function _valid_base64 (data) return data:find("^[0-9a-zA-Z/+]*$") end
+
+local function _split_path (path)
+ local _, _, mediatype, data = path:find("^([^,]*),(.*)")
+ if not mediatype then return "must have comma in path" end
+ local base64 = false
+ if mediatype:find(";base64$") then
+ base64 = true
+ mediatype = mediatype:sub(1, -8)
+ end
+ if base64 and not _valid_base64(data) then
+ return "illegal character in base64 encoding"
+ end
+ return nil, mediatype, base64, data
+end
+
+function M.init (self)
+ if M._SUPER.host(self) then
+ return nil, "data URIs may not have authority parts"
+ end
+ local err, mediatype, base64, data = _split_path(M._SUPER.path(self))
+ if err then return nil, "invalid data URI (" .. err .. ")" end
+ return self
+end
+
+function M.data_media_type (self, ...)
+ local _, old, base64, data = _split_path(M._SUPER.path(self))
+
+ if select('#', ...) > 0 then
+ local new = ... or ""
+ new = Util.uri_encode(new, "^A-Za-z0-9%-._~!$&'()*+;=:@/")
+ if base64 then new = new .. ";base64" end
+ M._SUPER.path(self, new .. "," .. data)
+ end
+
+ if old ~= "" then
+ if old:find("^;") then old = "text/plain" .. old end
+ return Util.uri_decode(old)
+ else
+ return "text/plain;charset=US-ASCII" -- default type
+ end
+end
+
+local function _urienc_len (s)
+ local num_unsafe_chars = s:gsub("[A-Za-z0-9%-._~!$&'()*+,;=:@/]", ""):len()
+ local num_safe_chars = s:len() - num_unsafe_chars
+ return num_safe_chars + num_unsafe_chars * 3
+end
+
+local function _base64_len (s)
+ local num_blocks = (s:len() + 2) / 3
+ num_blocks = num_blocks - num_blocks % 1
+ return num_blocks * 4
+ + 7 -- because of ";base64" marker
+end
+
+local function _do_filter (algorithm, input)
+ return Filter[algorithm](input)
+end
+
+function M.data_bytes (self, ...)
+ local _, mediatype, base64, old = _split_path(M._SUPER.path(self))
+ if base64 then
+ if not Filter then
+ error("'datafilter' Lua module required to decode base64 data")
+ end
+ old = _do_filter("base64_decode", old)
+ else
+ old = Util.uri_decode(old)
+ end
+
+ if select('#', ...) > 0 then
+ local new = ... or ""
+ local urienc_len = _urienc_len(new)
+ local base64_len = _base64_len(new)
+ if base64_len < urienc_len and Filter then
+ mediatype = mediatype .. ";base64"
+ new = _do_filter("base64_encode", new)
+ else
+ new = new:gsub("%%", "%%25")
+ end
+ M._SUPER.path(self, mediatype .. "," .. new)
+ end
+
+ return old
+end
+
+function M.path (self, ...)
+ local old = M._SUPER.path(self)
+
+ if select('#', ...) > 0 then
+ local new = ...
+ if not new then error("there must be a path in a data URI") end
+ local err = _split_path(new)
+ if err then error("invalid data URI (" .. err .. ")") end
+ M._SUPER.path(self, new)
+ end
+
+ return old
+end
+
+Util.uri_part_not_allowed(M, "userinfo")
+Util.uri_part_not_allowed(M, "host")
+Util.uri_part_not_allowed(M, "port")
+
+return M
+-- vi:ts=4 sw=4 expandtab
diff --git a/server/src/3rd/lua-uri/uri/file.lua b/server/src/3rd/lua-uri/uri/file.lua
new file mode 100644
index 00000000..271cb3ed
--- /dev/null
+++ b/server/src/3rd/lua-uri/uri/file.lua
@@ -0,0 +1,72 @@
+local M = { _NAME = "uri.file" }
+local Util = require "uri._util"
+local URI = require "uri"
+Util.subclass_of(M, URI)
+
+function M.init (self)
+ if self:userinfo() or self:port() then
+ return nil, "usernames and passwords are not allowed in HTTP URIs"
+ end
+
+ local host = self:host()
+ local path = self:path()
+ if host then
+ if host:lower() == "localhost" then self:host("") end
+ else
+ if not path:find("^/") then
+ return nil, "file URIs must contain a host, even if it's empty"
+ end
+ self:host("")
+ end
+
+ if path == "" then self:path("/") end
+
+ return self
+end
+
+function M.host (self, ...)
+ local old = M._SUPER.host(self)
+
+ if select('#', ...) > 0 then
+ local new = ...
+ if not new then error("file URIs must have an authority part") end
+ if new:lower() == "localhost" then new = "" end
+ M._SUPER.host(self, new)
+ end
+
+ return old
+end
+
+function M.path (self, ...)
+ local old = M._SUPER.path(self)
+
+ if select('#', ...) > 0 then
+ local new = ...
+ if not new or new == "" then new = "/" end
+ M._SUPER.path(self, new)
+ end
+
+ return old
+end
+
+local function _os_implementation (os)
+ local FileImpl = Util.attempt_require("uri.file." .. os:lower())
+ if not FileImpl then
+ error("no file URI implementation for operating system " .. os)
+ end
+ return FileImpl
+end
+
+function M.filesystem_path (self, os)
+ return _os_implementation(os).filesystem_path(self)
+end
+
+function M.make_file_uri (path, os)
+ return _os_implementation(os).make_file_uri(path)
+end
+
+Util.uri_part_not_allowed(M, "userinfo")
+Util.uri_part_not_allowed(M, "port")
+
+return M
+-- vi:ts=4 sw=4 expandtab
diff --git a/server/src/3rd/lua-uri/uri/file/unix.lua b/server/src/3rd/lua-uri/uri/file/unix.lua
new file mode 100644
index 00000000..2ffe1504
--- /dev/null
+++ b/server/src/3rd/lua-uri/uri/file/unix.lua
@@ -0,0 +1,26 @@
+local M = { _NAME = "uri.file.unix" }
+local URI = require "uri"
+local Util = require "uri._util"
+
+function M.filesystem_path (uri)
+ if uri:host() ~= "" then
+ error("a file URI with a host name can't be converted to a Unix path")
+ end
+ local path = uri:path()
+ if path:find("%%00") or path:find("%%2F") then
+ error("Unix paths cannot contain encoded null bytes or slashes")
+ end
+ return Util.uri_decode(path)
+end
+
+function M.make_file_uri (path)
+ if not path:find("^/") then
+ error("Unix relative paths can't be converted to file URIs")
+ end
+ path = path:gsub("//+", "/")
+ path = Util.uri_encode(path, "^A-Za-z0-9%-._~!$&'()*+,;=:@/")
+ return assert(URI:new("file://" .. path))
+end
+
+return M
+-- vi:ts=4 sw=4 expandtab
diff --git a/server/src/3rd/lua-uri/uri/file/win32.lua b/server/src/3rd/lua-uri/uri/file/win32.lua
new file mode 100644
index 00000000..bdacbe15
--- /dev/null
+++ b/server/src/3rd/lua-uri/uri/file/win32.lua
@@ -0,0 +1,34 @@
+local M = { _NAME = "uri.file.win32" }
+local URI = require "uri"
+local Util = require "uri._util"
+
+function M.filesystem_path (uri)
+ local host = uri:host()
+ local path = Util.uri_decode(uri:path())
+ if host ~= "" then path = "//" .. host .. path end
+ if path:find("^/[A-Za-z]|/") or path:find("^/[A-Za-z]|$") then
+ path = path:gsub("|", ":", 1)
+ end
+ if path:find("^/[A-Za-z]:/") then
+ path = path:sub(2)
+ elseif path:find("^/[A-Za-z]:$") then
+ path = path:sub(2) .. "/"
+ end
+ path = path:gsub("/", "\\")
+ return path
+end
+
+function M.make_file_uri (path)
+ if path:find("^[A-Za-z]:$") then path = path .. "\\" end
+ local _, _, host, hostpath = path:find("^\\\\([A-Za-z.]+)\\(.*)$")
+ host = host or ""
+ hostpath = hostpath or path
+ hostpath = hostpath:gsub("\\", "/")
+ :gsub("//+", "/")
+ hostpath = Util.uri_encode(hostpath, "^A-Za-z0-9%-._~!$&'()*+,;=:@/")
+ if not hostpath:find("^/") then hostpath = "/" .. hostpath end
+ return assert(URI:new("file://" .. host .. hostpath))
+end
+
+return M
+-- vi:ts=4 sw=4 expandtab
diff --git a/server/src/3rd/lua-uri/uri/ftp.lua b/server/src/3rd/lua-uri/uri/ftp.lua
new file mode 100644
index 00000000..2d9e3f6c
--- /dev/null
+++ b/server/src/3rd/lua-uri/uri/ftp.lua
@@ -0,0 +1,53 @@
+local M = { _NAME = "uri.ftp" }
+local Util = require "uri._util"
+local LoginURI = require "uri._login"
+Util.subclass_of(M, LoginURI)
+
+function M.default_port () return 21 end
+
+function M.init (self)
+ self, err = M._SUPER.init_base(self)
+ if not self then return nil, err end
+
+ local host = self:host()
+ if not host or host == "" then
+ return nil, "FTP URIs must have a hostname"
+ end
+
+ -- I don't think there's any distinction in FTP URIs between empty path
+ -- and the root directory, so probably best to normalize as we do for HTTP.
+ if self:path() == "" then self:path("/") end
+
+ return self
+end
+
+function M.path (self, ...)
+ local old = M._SUPER.path(self)
+
+ if select("#", ...) > 0 then
+ local new = ...
+ if not new or new == "" then new = "/" end
+ M._SUPER.path(self, new)
+ end
+
+ return old
+end
+
+function M.ftp_typecode (self, ...)
+ local path = M._SUPER.path(self)
+ local _, _, withouttype, old = path:find("^(.*);type=(.*)$")
+ if not withouttype then withouttype = path end
+ if old == "" then old = nil end
+
+ if select("#", ...) > 0 then
+ local new = ...
+ if not new then new = "" end
+ if new ~= "" then new = ";type=" .. new end
+ M._SUPER.path(self, withouttype .. new)
+ end
+
+ return old
+end
+
+return M
+-- vi:ts=4 sw=4 expandtab
diff --git a/server/src/3rd/lua-uri/uri/http.lua b/server/src/3rd/lua-uri/uri/http.lua
new file mode 100644
index 00000000..91f7a57f
--- /dev/null
+++ b/server/src/3rd/lua-uri/uri/http.lua
@@ -0,0 +1,32 @@
+local M = { _NAME = "uri.http" }
+local Util = require "uri._util"
+local URI = require "uri"
+Util.subclass_of(M, URI)
+
+-- This implementation is based on RFC 2616 section 3.2 and RFC 1738
+-- section 3.3.
+--
+-- An HTTP URI with a 'userinfo' field is considered invalid, because it isn't
+-- shown in the syntax given in RFC 2616, and is explicitly disallowed by
+-- RFC 1738.
+
+function M.default_port () return 80 end
+
+function M.init (self)
+ if self:userinfo() then
+ return nil, "usernames and passwords are not allowed in HTTP URIs"
+ end
+
+ -- RFC 2616 section 3.2.3 says that this is OK, but not that using the
+ -- redundant slash is canonical. I'm adding it because browsers tend to
+ -- treat the version with the extra slash as the normalized form, and
+ -- the initial slash is always present in an HTTP GET request.
+ if self:path() == "" then self:path("/") end
+
+ return self
+end
+
+Util.uri_part_not_allowed(M, "userinfo")
+
+return M
+-- vi:ts=4 sw=4 expandtab
diff --git a/server/src/3rd/lua-uri/uri/https.lua b/server/src/3rd/lua-uri/uri/https.lua
new file mode 100644
index 00000000..0c4c8bc3
--- /dev/null
+++ b/server/src/3rd/lua-uri/uri/https.lua
@@ -0,0 +1,9 @@
+local M = { _NAME = "uri.https" }
+local Util = require "uri._util"
+local Http = require "uri.http"
+Util.subclass_of(M, Http)
+
+function M.default_port () return 443 end
+
+return M
+-- vi:ts=4 sw=4 expandtab
diff --git a/server/src/3rd/lua-uri/uri/pop.lua b/server/src/3rd/lua-uri/uri/pop.lua
new file mode 100644
index 00000000..e42d9d41
--- /dev/null
+++ b/server/src/3rd/lua-uri/uri/pop.lua
@@ -0,0 +1,111 @@
+local M = { _NAME = "uri.pop" }
+local URI = require "uri"
+local Util = require "uri._util"
+Util.subclass_of(M, URI)
+
+-- This is the set of characters must be encoded in a POP userinfo, which
+-- unlike for other schemes includes the ';' character.
+local _POP_USERINFO_ENCODE = "^A-Za-z0-9%-._~%%!$&'()*+,=:"
+
+function M.default_port () return 110 end
+
+local function _update_userinfo (self, old, new)
+ if new then
+ local _, _, user, auth = new:find("^(.*);[Aa][Uu][Tt][Hh]=(.*)$")
+ if not user then user = new end
+ if user == "" then return "pop user name must not be empty" end
+ user = Util.uri_encode(user, _POP_USERINFO_ENCODE)
+ if auth then
+ if auth == "" then return "pop auth type must not be empty" end
+ if auth == "*" then auth = nil end
+ auth = Util.uri_encode(auth, _POP_USERINFO_ENCODE)
+ end
+ new = user .. (auth and ";auth=" .. auth or "")
+ end
+
+ if new ~= old then M._SUPER.userinfo(self, new) end
+ return nil
+end
+
+function M.init (self)
+ if M._SUPER.path(self) ~= "" then
+ return nil, "pop URIs must have an empty path"
+ end
+
+ local userinfo = M._SUPER.userinfo(self)
+ local err = _update_userinfo(self, userinfo, userinfo)
+ if err then return nil, err end
+
+ return self
+end
+
+function M.userinfo (self, ...)
+ local old = M._SUPER.userinfo(self)
+
+ if select('#', ...) > 0 then
+ local new = ...
+ local err = _update_userinfo(self, old, new)
+ if err then error(err) end
+ end
+
+ return old
+end
+
+function M.path (self, new)
+ if new and new ~= "" then error("POP URIs must have an empty path") end
+ return ""
+end
+
+local function _decode_userinfo (self)
+ local old = M._SUPER.userinfo(self)
+ if not old then return nil, nil end
+ local _, _, old_user, old_auth = old:find("^(.*);auth=(.*)$")
+ if not old_user then old_user = old end
+ return old_user, old_auth
+end
+
+function M.pop_user (self, ...)
+ local old_user, old_auth = _decode_userinfo(self)
+
+ if select('#', ...) > 0 then
+ local new = ...
+ if new == "" then error("pop user name must not be empty") end
+ if not new and old_auth then
+ error("pop user name required when an auth type is specified")
+ end
+ if new then
+ new = Util.uri_encode(new, _POP_USERINFO_ENCODE)
+ if old_auth then new = new .. ";auth=" .. old_auth end
+ end
+ M._SUPER.userinfo(self, new)
+ end
+
+ return Util.uri_decode(old_user)
+end
+
+function M.pop_auth (self, ...)
+ local old_user, old_auth = _decode_userinfo(self)
+
+ if select('#', ...) > 0 then
+ local new = ...
+ if not new or new == ""
+ then error("pop auth type must not be empty")
+ end
+ if new == "*" then new = nil end
+ if new and not old_user then
+ error("pop auth type can't be specified without user name")
+ end
+ if new then
+ new = old_user .. ";auth=" ..
+ Util.uri_encode(new, _POP_USERINFO_ENCODE)
+ else
+ new = old_user
+ end
+ M._SUPER.userinfo(self, new)
+ end
+
+ return old_auth and Util.uri_decode(old_auth) or "*"
+end
+
+return M
+-- vi:ts=4 sw=4 expandtab
diff --git a/server/src/3rd/lua-uri/uri/rtsp.lua b/server/src/3rd/lua-uri/uri/rtsp.lua
new file mode 100644
index 00000000..03c71485
--- /dev/null
+++ b/server/src/3rd/lua-uri/uri/rtsp.lua
@@ -0,0 +1,9 @@
+local M = { _NAME = "uri.rtsp" }
+local Util = require "uri._util"
+local HttpURI = require "uri.http"
+Util.subclass_of(M, HttpURI)
+
+function M.default_port () return 554 end
+
+return M
+-- vi:ts=4 sw=4 expandtab
diff --git a/server/src/3rd/lua-uri/uri/rtspu.lua b/server/src/3rd/lua-uri/uri/rtspu.lua
new file mode 100644
index 00000000..16f5e3ee
--- /dev/null
+++ b/server/src/3rd/lua-uri/uri/rtspu.lua
@@ -0,0 +1,7 @@
+local M = { _NAME = "uri.rtspu" }
+local Util = require "uri._util"
+local RtspURI = require "uri.rtsp"
+Util.subclass_of(M, RtspURI)
+
+return M
+-- vi:ts=4 sw=4 expandtab
diff --git a/server/src/3rd/lua-uri/uri/telnet.lua b/server/src/3rd/lua-uri/uri/telnet.lua
new file mode 100644
index 00000000..339e21ee
--- /dev/null
+++ b/server/src/3rd/lua-uri/uri/telnet.lua
@@ -0,0 +1,38 @@
+local M = { _NAME = "uri.telnet" }
+local Util = require "uri._util"
+local LoginURI = require "uri._login"
+Util.subclass_of(M, LoginURI)
+
+function M.default_port () return 23 end
+
+function M.init (self)
+ self, err = M._SUPER.init_base(self)
+ if not self then return nil, err end
+
+ -- RFC 4248 does not discuss what a path longer than '/' might mean, and
+ -- there are no examples with anything significant in the path, so I'm
+ -- assuming that extra information in the path is not allowed.
+ local path = M._SUPER.path(self)
+ if path ~= "" and path ~= "/" then
+ return nil, "superfluous information in path of telnet URI"
+ end
+
+ -- RFC 4248 section 2 says that the '/' can be omitted. I chose to
+ -- normalize to having it there, since the example shown in the RFC has
+ -- it, and this is consistent with the way I treat HTTP URIs.
+ if path == "" then self:path("/") end
+
+ return self
+end
+
+-- The path is always '/', so setting it won't do anything, but we do throw
+-- an exception on an attempt to set it to anything invalid.
+function M.path (self, new)
+ if new and new ~= "" and new ~= "/" then
+ error("invalid path for telnet URI")
+ end
+ return "/"
+end
+
+return M
+-- vi:ts=4 sw=4 expandtab
diff --git a/server/src/3rd/lua-uri/uri/urn.lua b/server/src/3rd/lua-uri/uri/urn.lua
new file mode 100644
index 00000000..aa4b1776
--- /dev/null
+++ b/server/src/3rd/lua-uri/uri/urn.lua
@@ -0,0 +1,131 @@
+local M = { _NAME = "uri.urn" }
+local Util = require "uri._util"
+local URI = require "uri"
+Util.subclass_of(M, URI)
+
+-- This implements RFC 2141, and attempts to change the class of the URI object
+-- to one of its subclasses for further validation and normalization of the
+-- namespace-specific string.
+
+-- Check NID syntax matches RFC 2141 section 2.1.
+local function _valid_nid (nid)
+ if nid == "" then return nil, "missing completely" end
+ if nid:len() > 32 then return nil, "too long" end
+ if not nid:find("^[A-Za-z0-9][-A-Za-z0-9]*$") then
+ return nil, "contains illegal character"
+ end
+ if nid:lower() == "urn" then return nil, "'urn' is reserved" end
+ return true
+end
+
+-- Check NSS syntax matches RFC 2141 section 2.2.
+local function _valid_nss (nss)
+ if nss == "" then return nil, "can't be empty" end
+ if nss:find("[^A-Za-z0-9()+,%-.:=@;$_!*'/%%]") then
+ return nil, "contains illegal character"
+ end
+ return true
+end
+
+local function _validate_and_normalize_path (path)
+ local _, _, nid, nss = path:find("^([^:]+):(.*)$")
+ if not nid then return nil, "illegal path syntax for URN" end
+
+ local ok, msg = _valid_nid(nid)
+ if not ok then
+ return nil, "invalid namespace identifier (" .. msg .. ")"
+ end
+ ok, msg = _valid_nss(nss)
+ if not ok then
+ return nil, "invalid namespace specific string (" .. msg .. ")"
+ end
+
+ return nid:lower() .. ":" .. nss
+end
+
+-- TODO - this should check that percent-encoded bytes are valid UTF-8
+function M.init (self)
+ if M._SUPER.query(self) then
+ return nil, "URNs may not have query parts"
+ end
+ if M._SUPER.host(self) then
+ return nil, "URNs may not have authority parts"
+ end
+
+ local path, msg = _validate_and_normalize_path(self:path())
+ if not path then return nil, msg end
+ M._SUPER.path(self, path)
+
+ local nid_class
+ = Util.attempt_require("uri.urn." .. self:nid():gsub("%-", "_"))
+ if nid_class then
+ setmetatable(self, nid_class)
+ if self.init ~= M.init then return self:init() end
+ end
+
+ return self
+end
+
+function M.nid (self, new)
+ local _, _, old = self:path():find("^([^:]+)")
+
+ if new then
+ new = new:lower()
+ if new ~= old then
+ local ok, msg = _valid_nid(new)
+ if not ok then
+ error("invalid namespace identifier (" .. msg .. ")")
+ end
+ end
+ Util.do_class_changing_change(self, M, "NID", new, function (uri, new)
+ M._SUPER.path(uri, new .. ":" .. uri:nss())
+ end)
+ end
+
+ return old
+end
+
+function M.nss (self, new)
+ local _, _, old = self:path():find(":(.*)")
+
+ if new and new ~= old then
+ local ok, msg = _valid_nss(new)
+ if not ok then
+ error("invalid namespace specific string (" .. msg .. ")")
+ end
+ M._SUPER.path(self, self:nid() .. ":" .. new)
+ end
+
+ return old
+end
+
+function M.path (self, new)
+ local old = M._SUPER.path(self)
+
+ if new and new ~= old then
+ local path, msg = _validate_and_normalize_path(new)
+ if not path then
+ error("invalid path for URN '" .. new .. "' (" ..msg .. ")")
+ end
+ local _, _, newnid, newnss = path:find("^([^:]+):(.*)")
+ if not newnid then error("bad path for URN, no NID part found") end
+ local ok, msg = _valid_nid(newnid)
+ if not ok then error("invalid namespace identifier (" .. msg .. ")") end
+ if newnid:lower() == self:nid() then
+ self:nss(newnss)
+ else
+ Util.do_class_changing_change(self, M, "path", path,
+ function (uri, new) M._SUPER.path(uri, new) end)
+ end
+ end
+
+ return old
+end
+
+Util.uri_part_not_allowed(M, "userinfo")
+Util.uri_part_not_allowed(M, "host")
+Util.uri_part_not_allowed(M, "port")
+Util.uri_part_not_allowed(M, "query")
+
+return M
+-- vi:ts=4 sw=4 expandtab
diff --git a/server/src/3rd/lua-uri/uri/urn/isbn.lua b/server/src/3rd/lua-uri/uri/urn/isbn.lua
new file mode 100644
index 00000000..5f0bdb69
--- /dev/null
+++ b/server/src/3rd/lua-uri/uri/urn/isbn.lua
@@ -0,0 +1,67 @@
+local M = { _NAME = "uri.urn.isbn" }
+local Util = require "uri._util"
+local URN = require "uri.urn"
+Util.subclass_of(M, URN)
+
+-- This implements the 'isbn' NID defined in RFC 3187, and is consistent
+-- with the same NID suggested in RFC 2288.
+
+local function _valid_isbn (isbn)
+ if not isbn:find("^[-%d]+[%dXx]$") then return nil, "invalid character" end
+ local ISBN = Util.attempt_require("isbn")
+ if ISBN then return ISBN:new(isbn) end
+ return isbn
+end
+
+local function _normalize_isbn (isbn)
+ isbn = isbn:gsub("%-", ""):upper()
+ local ISBN = Util.attempt_require("isbn")
+ if ISBN then return tostring(ISBN:new(isbn)) end
+ return isbn
+end
+
+function M.init (self)
+ local nss = self:nss()
+ local ok, msg = _valid_isbn(nss)
+ if not ok then return nil, "invalid ISBN value (" .. msg .. ")" end
+ self:nss(_normalize_isbn(nss))
+ return self
+end
+
+function M.nss (self, new)
+ local old = M._SUPER.nss(self)
+
+ if new then
+ local ok, msg = _valid_isbn(new)
+ if not ok then
+ error("bad ISBN value '" .. new .. "' (" .. msg .. ")")
+ end
+ M._SUPER.nss(self, _normalize_isbn(new))
+ end
+
+ return old
+end
+
+function M.isbn_digits (self, new)
+ local old = self:nss():gsub("%-", "")
+
+ if new then
+ local ok, msg = _valid_isbn(new)
+ if not ok then
+ error("bad ISBN value '" .. new .. "' (" .. msg .. ")")
+ end
+ self._SUPER.nss(self, _normalize_isbn(new))
+ end
+
+ return old
+end
+
+function M.isbn (self, new)
+ local ISBN = require "isbn"
+ local old = ISBN:new(self:nss())
+ if new then self:nss(tostring(new)) end
+ return old
+end
+
+return M
+-- vi:ts=4 sw=4 expandtab
diff --git a/server/src/3rd/lua-uri/uri/urn/issn.lua b/server/src/3rd/lua-uri/uri/urn/issn.lua
new file mode 100644
index 00000000..c5f37f8c
--- /dev/null
+++ b/server/src/3rd/lua-uri/uri/urn/issn.lua
@@ -0,0 +1,65 @@
+local M = { _NAME = "uri.urn.issn" }
+local Util = require "uri._util"
+local URN = require "uri.urn"
+Util.subclass_of(M, URN)
+
+local function _parse_issn (issn)
+ local _, _, nums1, nums2, checksum
+ = issn:find("^(%d%d%d%d)-?(%d%d%d)([%dxX])$")
+ if checksum == "x" then checksum = "X" end
+ return nums1, nums2, checksum
+end
+
+local function _valid_issn (issn)
+ local nums1, nums2, actual_checksum = _parse_issn(issn)
+ if not nums1 then return nil, "invalid ISSN syntax" end
+ local nums = nums1 .. nums2
+
+ local expected_checksum = 0
+ for i = 1, 7 do
+ expected_checksum = expected_checksum + tonumber(nums:sub(i, i)) * (9 - i)
+ end
+ expected_checksum = (11 - expected_checksum % 11) % 11
+ expected_checksum = (expected_checksum == 10) and "X"
+ or tostring(expected_checksum)
+ if actual_checksum ~= expected_checksum then
+ return nil, "wrong checksum, expected " .. expected_checksum
+ end
+
+ return true
+end
+
+local function _normalize_issn (issn)
+ local nums1, nums2, checksum = _parse_issn(issn)
+ return nums1 .. "-" .. nums2 .. checksum
+end
+
+function M.init (self)
+ local nss = self:nss()
+ local ok, msg = _valid_issn(nss)
+ if not ok then return nil, "bad NSS value for ISSN URI (" .. msg .. ")" end
+ M._SUPER.nss(self, _normalize_issn(nss))
+ return self
+end
+
+function M.nss (self, new)
+ local old = M._SUPER.nss(self)
+
+ if new then
+ local ok, msg = _valid_issn(new)
+ if not ok then
+ error("bad ISSN value '" .. new .. "' (" .. msg .. ")")
+ end
+ M._SUPER.nss(self, _normalize_issn(new))
+ end
+
+ return old
+end
+
+function M.issn_digits (self, new)
+ local old = self:nss(new)
+ return old:sub(1, 4) .. old:sub(6, 9)
+end
+
+return M
+-- vi:ts=4 sw=4 expandtab
diff --git a/server/src/3rd/lua-uri/uri/urn/oid.lua b/server/src/3rd/lua-uri/uri/urn/oid.lua
new file mode 100644
index 00000000..37110cda
--- /dev/null
+++ b/server/src/3rd/lua-uri/uri/urn/oid.lua
@@ -0,0 +1,62 @@
+local M = { _NAME = "uri.urn.oid" }
+local Util = require "uri._util"
+local URN = require "uri.urn"
+Util.subclass_of(M, URN)
+
+-- This implements RFC 3061.
+
+local function _valid_oid (oid)
+ if oid == "" then return nil, "OID can't be zero-length" end
+ if not oid:find("^[.0-9]*$") then return nil, "bad character in OID" end
+ if oid:find("%.%.") then return nil, "missing number in OID" end
+ if oid:find("^0[^.]") or oid:find("%.0[^.]") then
+ return nil, "OID numbers shouldn't have leading zeros"
+ end
+ return true
+end
+
+function M.init (self)
+ local nss = self:nss()
+ local ok, msg = _valid_oid(nss)
+ if not ok then return nil, "bad NSS value for OID URI (" .. msg .. ")" end
+ return self
+end
+
+function M.nss (self, new)
+ local old = M._SUPER.nss(self)
+
+ if new then
+ local ok, msg = _valid_oid(new)
+ if not ok then
+ error("bad OID value '" .. new .. "' (" .. msg .. ")")
+ end
+ M._SUPER.nss(self, new)
+ end
+
+ return old
+end
+
+function M.oid_numbers (self, new)
+ local old = Util.split("%.", self:nss())
+ for i = 1, #old do old[i] = tonumber(old[i]) end
+
+ if new then
+ if type(new) ~= "table" then error("expected array of numbers") end
+ local nss = ""
+ for _, n in ipairs(new) do
+ if type(n) == "string" and n:find("^%d+$") then n = tonumber(n) end
+ if type(n) ~= "number" then error("bad type for number in OID") end
+ n = n - n % 1
+ if n < 0 then error("negative numbers not allowed in OID") end
+ if nss ~= "" then nss = nss .. "." end
+ nss = nss .. n
+ end
+ if nss == "" then error("no numbers in new OID value") end
+ self:nss(nss)
+ end
+
+ return old
+end
+
+return M
+-- vi:ts=4 sw=4 expandtab
diff --git a/server/src/sandbox.lua b/server/src/sandbox.lua
new file mode 100644
index 00000000..c09230fd
--- /dev/null
+++ b/server/src/sandbox.lua
@@ -0,0 +1,163 @@
+local function standard(loaded)
+ local r = {}
+ for _, s in ipairs {
+ --'package',
+ 'coroutine',
+ 'table',
+ --'io',
+ 'os',
+ 'string',
+ 'math',
+ 'utf8',
+ 'debug',
+ } do
+ r[s] = _G[s]
+ loaded[s] = _G[s]
+ end
+ for _, s in ipairs {
+ 'assert',
+ 'collectgarbage',
+ --'dofile',
+ 'error',
+ 'getmetatable',
+ 'ipairs',
+ --'loadfile',
+ 'load',
+ 'next',
+ 'pairs',
+ 'pcall',
+ 'print',
+ 'rawequal',
+ 'rawlen',
+ 'rawget',
+ 'rawset',
+ 'select',
+ 'setmetatable',
+ 'tonumber',
+ 'tostring',
+ 'type',
+ 'xpcall',
+ '_VERSION',
+ --'require',
+ } do
+ r[s] = _G[s]
+ end
+ return r
+end
+
+local function sandbox_env(loadlua, openfile, loaded)
+ local _LOADED = loaded or {}
+ local _E = standard(_LOADED)
+ local _PRELOAD = {}
+
+ _E.io = {
+ open = openfile,
+ }
+
+ local function searchpath(name, path)
+ local err = ''
+ name = string.gsub(name, '%.', '/')
+ for c in string.gmatch(path, '[^;]+') do
+ local filename = string.gsub(c, '%?', name)
+ local f = openfile(filename)
+ if f then
+ f:close()
+ return filename
+ end
+ err = err .. ("\n\tno file '%s'"):format(filename)
+ end
+ return nil, err
+ end
+
+ local function searcher_preload(name)
+ assert(type(_PRELOAD) == "table", "'package.preload' must be a table")
+ if _PRELOAD[name] == nil then
+ return ("\n\tno field package.preload['%s']"):format(name)
+ end
+ return _PRELOAD[name]
+ end
+
+ local function searcher_lua(name)
+ assert(type(_E.package.path) == "string", "'package.path' must be a string")
+ local filename, err = searchpath(name, _E.package.path)
+ if not filename then
+ return err
+ end
+ local f, err = loadlua(filename)
+ if not f then
+ error(("error loading module '%s' from file '%s':\n\t%s"):format(name, filename, err))
+ end
+ return f, filename
+ end
+
+ local function require_load(name)
+ local msg = ''
+ local _SEARCHERS = _E.package.searchers
+ assert(type(_SEARCHERS) == "table", "'package.searchers' must be a table")
+ for _, searcher in ipairs(_SEARCHERS) do
+ local f, extra = searcher(name)
+ if type(f) == 'function' then
+ return f, extra
+ elseif type(f) == 'string' then
+ msg = msg .. f
+ end
+ end
+ error(("module '%s' not found:%s"):format(name, msg))
+ end
+
+ _E.require = function(name)
+ assert(type(name) == "string", ("bad argument #1 to 'require' (string expected, got %s)"):format(type(name)))
+ local p = _LOADED[name]
+ if p ~= nil then
+ return p
+ end
+ local init, extra = require_load(name)
+ debug.setupvalue(init, 1, _E)
+ local res = init(name, extra)
+ if res ~= nil then
+ _LOADED[name] = res
+ end
+ if _LOADED[name] == nil then
+ _LOADED[name] = true
+ end
+ return _LOADED[name]
+ end
+ _E.package = {
+ config = [[
+ \
+ ;
+ ?
+ !
+ -
+ ]],
+ loaded = _LOADED,
+ path = '?.lua',
+ preload = _PRELOAD,
+ searchers = { searcher_preload, searcher_lua },
+ searchpath = searchpath
+ }
+ return _E
+end
+
+return function(name, root, io_open, loaded)
+ if not root:sub(-1):find '[/\\]' then
+ root = root .. '/'
+ end
+ local function openfile(name, mode)
+ return io_open(root .. name, mode)
+ end
+ local function loadlua(name)
+ local f = openfile(name, 'r')
+ if f then
+ local str = f:read 'a'
+ f:close()
+ return load(str, '@' .. root .. name)
+ end
+ end
+ local init = loadlua(name)
+ if not init then
+ return
+ end
+ debug.setupvalue(init, 1, sandbox_env(loadlua, openfile, loaded))
+ return init()
+end
diff --git a/server/src/uri.lua b/server/src/uri.lua
index a9b64177..35de8a74 100644
--- a/server/src/uri.lua
+++ b/server/src/uri.lua
@@ -1,58 +1,23 @@
local fs = require 'bee.filesystem'
local platform = require 'bee.platform'
+local sandbox = require 'sandbox'
+local luaUriPath = (ROOT / 'src' / '3rd' / 'lua-uri'):string()
+local URI = sandbox('uri.lua', luaUriPath, io.open)
+local URI_FILE = sandbox('uri/file.lua', luaUriPath, io.open)
+local OS = platform.OS == 'Windows' and 'win32' or 'unix'
local function decode(uri)
- -- Unix-like系统根是/
- if uri:sub(1, 9) == 'file:////' then
- return fs.path(uri:sub(9))
- end
- if uri:sub(1, 8) ~= 'file:///' then
- log.error('uri decode failed: ', uri)
- return nil
- end
-
- -- linux uri example: file:///home/user/project/
- if platform.OS == 'Linux' then
- return fs.path(uri:sub(8))
- end
-
- local names = {}
- for name in uri:sub(9):gmatch '[^%/]+' do
- names[#names+1] = name:gsub('%%([0-9a-fA-F][0-9a-fA-F])', function (hex)
- return string.char(tonumber(hex, 16))
- end)
- end
- if #names == 0 then
- log.error('uri decode failed: ', uri)
- return nil
- end
- -- 盘符后面加个斜杠
- local path = fs.path(names[1] .. '/')
- for i = 2, #names do
- path = path / names[i]
- end
- return fs.absolute(path)
+ local obj = URI:new(uri)
+ local fullPath = obj:filesystem_path(OS)
+ local path = fs.path(fullPath)
+ return path
end
local function encode(path)
- local names = {}
- local cur = fs.absolute(path)
- while true do
- local name = cur:filename():string()
- if name == '' then
- -- 盘符,去掉一个斜杠
- name = cur:string():sub(1, -2)
- end
- name = name:gsub([=[[^%w%-%_%.%~]]=], function (char)
- return ('%%%02X'):format(string.byte(char))
- end)
- table.insert(names, 1, name)
- if cur == cur:parent_path() then
- break
- end
- cur = cur:parent_path()
- end
- return 'file:///' .. table.concat(names, '/')
+ local fullPath = fs.absolute(path)
+ local obj = URI_FILE.make_file_uri(fullPath:string(), OS)
+ local uri = obj:uri()
+ return uri
end
return {
diff --git a/server/src/workspace.lua b/server/src/workspace.lua
index 4962e7e1..895a2680 100644
--- a/server/src/workspace.lua
+++ b/server/src/workspace.lua
@@ -151,6 +151,7 @@ function mt:init(rootUri)
return
end
log.info('Workspace inited, root: ', self.root)
+ log.info('Workspace inited, uri: ', rootUri)
local logPath = ROOT / 'log' / (rootUri:gsub('[/:]+', '_') .. '.log')
log.info('Log path: ', logPath)
log.init(ROOT, logPath)