From c3a1a6c7e4e2f4d34d37a350aa12514fea77ad4c Mon Sep 17 00:00:00 2001 From: sumneko Date: Mon, 20 May 2019 11:42:37 +0800 Subject: =?UTF-8?q?#37=20=E4=BD=BF=E7=94=A8uri=E5=BA=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/src/3rd/lua-uri/uri.lua | 504 ++++++++++++++++++++++++++++++ server/src/3rd/lua-uri/uri/_login.lua | 96 ++++++ server/src/3rd/lua-uri/uri/_relative.lua | 81 +++++ server/src/3rd/lua-uri/uri/_util.lua | 128 ++++++++ server/src/3rd/lua-uri/uri/data.lua | 116 +++++++ server/src/3rd/lua-uri/uri/file.lua | 72 +++++ server/src/3rd/lua-uri/uri/file/unix.lua | 26 ++ server/src/3rd/lua-uri/uri/file/win32.lua | 34 ++ server/src/3rd/lua-uri/uri/ftp.lua | 53 ++++ server/src/3rd/lua-uri/uri/http.lua | 32 ++ server/src/3rd/lua-uri/uri/https.lua | 9 + server/src/3rd/lua-uri/uri/pop.lua | 111 +++++++ server/src/3rd/lua-uri/uri/rtsp.lua | 9 + server/src/3rd/lua-uri/uri/rtspu.lua | 7 + server/src/3rd/lua-uri/uri/telnet.lua | 38 +++ server/src/3rd/lua-uri/uri/urn.lua | 131 ++++++++ server/src/3rd/lua-uri/uri/urn/isbn.lua | 67 ++++ server/src/3rd/lua-uri/uri/urn/issn.lua | 65 ++++ server/src/3rd/lua-uri/uri/urn/oid.lua | 62 ++++ server/src/sandbox.lua | 163 ++++++++++ server/src/uri.lua | 61 +--- server/src/workspace.lua | 1 + 22 files changed, 1818 insertions(+), 48 deletions(-) create mode 100644 server/src/3rd/lua-uri/uri.lua create mode 100644 server/src/3rd/lua-uri/uri/_login.lua create mode 100644 server/src/3rd/lua-uri/uri/_relative.lua create mode 100644 server/src/3rd/lua-uri/uri/_util.lua create mode 100644 server/src/3rd/lua-uri/uri/data.lua create mode 100644 server/src/3rd/lua-uri/uri/file.lua create mode 100644 server/src/3rd/lua-uri/uri/file/unix.lua create mode 100644 server/src/3rd/lua-uri/uri/file/win32.lua create mode 100644 server/src/3rd/lua-uri/uri/ftp.lua create mode 100644 server/src/3rd/lua-uri/uri/http.lua create mode 100644 server/src/3rd/lua-uri/uri/https.lua create mode 100644 server/src/3rd/lua-uri/uri/pop.lua create mode 100644 server/src/3rd/lua-uri/uri/rtsp.lua create mode 100644 server/src/3rd/lua-uri/uri/rtspu.lua create mode 100644 server/src/3rd/lua-uri/uri/telnet.lua create mode 100644 server/src/3rd/lua-uri/uri/urn.lua create mode 100644 server/src/3rd/lua-uri/uri/urn/isbn.lua create mode 100644 server/src/3rd/lua-uri/uri/urn/issn.lua create mode 100644 server/src/3rd/lua-uri/uri/urn/oid.lua create mode 100644 server/src/sandbox.lua (limited to 'server/src') 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) -- cgit v1.2.3