local URLutil = --[=[ Utilities for URL etc. on www. * decode * encode * getAuthority * getFragment * getHost * getLocation * getNormalized * getPath * getPort * getQuery * getQueryTable * getRelativePath * getScheme * getSortkey * getTLD * getTop2domain * getTop3domain * isAuthority * isDomain * isDomainExample * isDomainInt * isHost * isHostPathResource * isIP * isIPlocal * isIPv4 * isIPv6 * isMailAddress * isMailLink * isProtocolDialog * isProtocolWiki * isResourceURL * isSuspiciousURL * isUnescapedURL * isWebURL * wikiEscapeURL * failsafe Only [[dotted decimal]] notation for IPv4 expected.Does not support dotted hexadecimal, dotted octal, or single-number formats.IPv6 URL (bracketed) not yet implemented; might need Wikintax escaping anyway.]=]local Failsafe = URLutil
local decodeComponentProtect =
local decodeComponentEscape = function (averse, adapt) return adapt
127 or decodeComponentProtect[averse ]:find(string.char(adapt), 1, true)end -- decodeComponentEscape
local decodeComponentML = function (ask) local i = 1 local j, n, s while (i) do i = ask:find("&#[xX]%x%x+;", i) if i then j = ask:find(";", i + 3, true) s = ask:sub(i + 2, j - 1):upper n = s:byte(1, 1) if n
1 then ask = s .. ask:sub(j) else ask = string.format("%s%s%s", ask:sub(1, i - 1), s, ask:sub(j)) end end i = i + 1 end end -- while i return askend -- decodeComponentML
local decodeComponentPercent = function (ask, averse) local i = 1 local j, k, m, n while (i) do i = ask:find("%%[2-7]%x", i) if i then j = i + 1 k = j + 1 n = ask:byte(k, k) k = k + 1 m = (n > 96) if m then n = n - 32 m = n end if n > 57 then n = n - 55 else n = n - 48 end n = (ask:byte(j, j) - 48) * 16 + n if n
"%27" then j = i + 6 while (ask:sub(j, j + 2)
1 then ask = string.format("%c%s", n, ask:sub(k)) else ask = string.format("%s%c%s", ask:sub(1, i - 1), n, ask:sub(k)) end i = j end end -- while i return askend -- decodeComponentPercent
local getTopDomain = function (url, mode) local r = URLutil.getHost(url) if r then local pattern = "[%w%%%-]+%.%a[%w%-]*%a)$" if mode
local getHash = function (url) local r = url:find("#", 1, true) if r then local i = url:find("&#", 1, true) if i then local s while (i) do s = url:sub(i + 2) if s:match("^%d+;") or s:match("^x%x+;") then r = url:find("#", i + 4, true) if r then i = url:find("&#", i + 4, true) else i = false end else r = i + 1 i = false end end -- while i end end return rend -- getHash
URLutil.decode = function (url, enctype) local r, s if type(enctype)
"" then s = false else s = s:upper end end r = mw.text.encode(mw.uri.decode(url, s)) if r:find("[%[|%]]") then local k r, k = r:gsub("%[", "[") :gsub("|", "|") :gsub("%]", "]") end return rend -- URLutil.decode
URLutil.encode = function (url, enctype) local k, r, s if type(enctype)
"" then s = false else s = s:upper end end r = mw.uri.encode(url, s) k = r:byte(1, 1) if -- k
42 or -- * k
59 then -- ; r = string.format("%%%X%s", k, r:sub(2)) end if r:find("[%[|%]]") then r, k = r:gsub("%[", "%5B") :gsub("|", "%7C") :gsub("%]", "%5D") end return rend -- URLutil.encode
URLutil.getAuthority = function (url) local r if type(url)
":" then if port:find("^[1-9]") then r = (host .. ":" .. port) end elseif #port
URLutil.getFragment = function (url, decode) local r if type(url)
"string" then local encoding = mw.text.trim(decode) local launch if encoding
"WIKI" then r = r:gsub("%.(%x%x)", "%%%1") :gsub("_", " ") launch = true end if launch then r = mw.uri.decode(r, "PATH") end end else r = false end else r = nil end return rend -- URLutil.getFragment
URLutil.getHost = function (url) local r = URLutil.getAuthority(url) if r then r = mw.ustring.match(r, "^([%w%.%%_%-]+):?[%d]*$") end return rend -- URLutil.getHost
URLutil.getLocation = function (url) local r if type(url)
"" then r = false else local i i = getHash(r) if i then if i
URLutil.getNormalized = function (url) local r if type(url)
"" then r = false else r = decodeComponentML(r) end else r = false end if r then local k = r:find("//", 1, true) if k then local j = r:find("/", k + 2, true) local sF, sP, sQ if r:find("%%[2-7]%x") then local i = getHash(r) if i then sF = r:sub(i + 1) r = r:sub(1, i - 1) if sF
35 then -- '#' n = n - 1 r = r:sub(1, n) end if n > j then sP = r:sub(j + 1) end r = r:sub(1, j - 1) end r = mw.ustring.lower(r) .. "/" if sP then r = r .. sP end if sQ then r = r .. sQ end if sF then r = string.format("%s#%s", r, sF) end end r = r:gsub(" ", "%%20") :gsub("%[", "%%5B") :gsub("|", "%%7C") :gsub("%]", "%%5D") :gsub("%<", "%%3C") :gsub("%>", "%%3E") end return rend -- URLutil.getNormalized
URLutil.getPath = function (url) local r = URLutil.getRelativePath(url) if r then local s = r:match("^([^%?]*)%?") if s then r = s end s = r:match("^([^#]*)#") if s then r = s end end return rend -- URLutil.getPath
URLutil.getPort = function (url) local r = URLutil.getAuthority(url) if r then r = r:match(":([1-9][0-9]*)$") if r then r = tonumber(r) else r = false end end return rend -- URLutil.getPort
URLutil.getQuery = function (url, key, separator) local r = URLutil.getLocation(url) if r then r = r:match("^[^%?]*%?(.+)$") if r then if type(key)
"string" then s = mw.text.trim(separator) if s:match("^[&;,/]$") then sep = s end end s = string.format("%s%s%s", sep, r, sep) scan = string.format("%s%s=([^%s]*)%s", sep, key, sep, sep) r = s:match(scan) end end if not r then r = false end end return rend -- URLutil.getQuery
URLutil.getQueryTable = function (url, separator) local r = URLutil.getQuery(url) if r then local sep = "&" local n, pairs, s, set if type(separator)
URLutil.getRelativePath = function (url) local r if type(url)
"/" then s = s:match("/[^/]+(/.*)$") end end if s then r = mw.text.trim(s) elseif URLutil.isResourceURL(url) then r = "/" else r = false end else r = nil end return rend -- URLutil.getRelativePath
URLutil.getScheme = function (url) local r if type(url)
"//" then if colon
0 then r = "//" end end else r = nil end return rend -- URLutil.getScheme
URLutil.getSortkey = function (url) local r = url if type(url)
0 then scheme = "" else scheme = url:match("^%s*([a-zA-Z]*)://") end if scheme then local s = url:sub(i + 2) local comps, site, m, suffix scheme = scheme:lower i = s:find("/") if i and i > 1 then suffix = s:sub(i + 1) -- mw.uri.encode s = s:sub(1, i - 1) suffix = suffix:gsub("#", " ") else suffix = "" end site, m = s:match("^(.+)(:%d+)$") if not m then site = s m = 0 end comps = mw.text.split(site:lower, ".", true) r = "///" for i = #comps, 2, -1 do r = string.format("%s%s.", r, comps[i ]) end -- for --i r = string.format("%s%s %d %s: %s", r, comps[1 ], m, scheme, suffix) end end end return rend -- URLutil.getSortkey
URLutil.getTLD = function (url) local r = URLutil.getHost(url) if r then r = mw.ustring.match(r, "%w+%.(%a[%w%-]*%a)$") if not r then r = false end end return rend -- URLutil.getTLD
URLutil.getTop2domain = function (url) return getTopDomain(url, 2)end -- URLutil.getTop2domain
URLutil.getTop3domain = function (url) return getTopDomain(url, 3)end -- URLutil.getTop3domain
URLutil.isAuthority = function (s) local r if type(s)
":" then port = port:match("^[1-9][0-9]*$") if type(port) ~= "string" then r = false end elseif port ~= "" then r = false end r = URLutil.isHost(host) else r = nil end return rend -- URLutil.isAuthority
URLutil.isDomain = function (s) local r if type(s)
"string" then if mw.ustring.find(s, "^%w") then if mw.ustring.find(s, "..", 1, true) then r = false else r = true end end end else r = nil end return rend -- URLutil.isDomain
URLutil.isDomainExample = function (url) -- RFC 2606: example.com example.net example.org example.edu local r = getTopDomain(url, 2) if r then local s = r:lower:match("^example%.([a-z][a-z][a-z])$") if s then r = (s
"edu" or s
"org") else r = false end end return rend -- URLutil.isDomainExample
URLutil.isDomainInt = function (url) -- Internationalized Domain Name (Punycode) local r = URLutil.getHost(url) if r then if r:match("^[!-~]+$") then local s = "." .. r if s:find(".xn--", 1, true) then r = true else r = false end else r = true end end return rend -- URLutil.isDomainInt
URLutil.isHost = function (s) return URLutil.isDomain(s) or URLutil.isIP(s)end -- URLutil.isHost
URLutil.isHostPathResource = function (s) local r = URLutil.isResourceURL(s) if not r and s then r = URLutil.isResourceURL("//" .. mw.text.trim(s)) end return rend -- URLutil.isHostPathResource
URLutil.isIP = function (s) return URLutil.isIPv4(s) and 4 or URLutil.isIPv6(s) and 6end -- URLutil.isIP
URLutil.isIPlocal = function (s) -- IPv4 according to RFC 1918, RFC 1122; even any 0.0.0.0 (RFC 5735) local r = false local num = s:match("^ *([01][0-9]*)%.") if num then num = tonumber(num) if num
10 or num
169 then -- 169.254.*.* elseif num
192 then -- 192.168.*.* num = s:match("^ *0*192%.([0-9]+)%.") if num then num = tonumber(num) if num
URLutil.isIPv4 = function (s) local function legal(n) return (tonumber(n) < 256) end local r = false if type(s)
URLutil.isIPv6 = function (s) local dcolon, groups if type(s) ~= "string" or s:len
1 and groups < 8) or (dcolon
8)) and (s:len
1 and s
URLutil.isMailAddress = function (s) if type(s)
URLutil.isMailLink = function (s) if type(s)
"string" then if s:lower
local function isProtocolAccepted(prot, supplied) if type(prot)
"" then if colon ~= ":" and slashes
":" or slashes
"string" then return true end end end end return falseend -- isProtocolAccepted
URLutil.isProtocolDialog = function (prot) return isProtocolAccepted(prot, " mailto irc ircs ssh telnet ")end -- URLutil.isProtocolDialog
URLutil.isProtocolWiki = function (prot) return isProtocolAccepted(prot, " ftp ftps git http https nntp sftp svn worldwind ")end -- URLutil.isProtocolWiki
URLutil.isResourceURL = function (url) local scheme = URLutil.getScheme(url) if scheme then local s = " // http:// https:// ftp:// sftp:// " s = s:find(string.format(" %s ", scheme)) if s then if URLutil.getAuthority(url) then if not url:match("%S%s+%S") then local s1, s2 = url:match("^([^#]+)(#.*)$") if s2 then if url:match("^%s*[a-zA-Z]*:?//(.+)/") then return true end else return true end end end end end return falseend -- URLutil.isResourceURL
URLutil.isSuspiciousURL = function (url) if URLutil.isResourceURL(url) then local s = URLutil.getAuthority(url) local pat = "[%[|%]" .. mw.ustring.char(34, 8201, 45, 8207, 8234, 45, 8239, 8288) .. "]" if s:find("@") or url:find("") or url:find(pat) or url:find("[%.,]$") then return true end -- TODO zero width character ?? return false end return trueend -- URLutil.isSuspiciousURL
URLutil.isUnescapedURL = function (url, trailing) if type(trailing) ~= "string" then if URLutil.isWebURL(url) then if url:match("[%[|%]]") then return true end end end return falseend -- URLutil.isUnescapedURL
URLutil.isWebURL = function (url) if URLutil.getScheme(url) and URLutil.getAuthority(url) then if not url:find("%S%s+%S") and not url:find("", 1, true) then return true end end return falseend -- URLutil.isWebURL
URLutil.wikiEscapeURL = function (url) if url:find("[%[|%]]") then local n url, n = url:gsub("%[", "[") :gsub("|", "|") :gsub("%]", "]") end return urlend -- URLutil.wikiEscapeURL
Failsafe.failsafe = function (atleast) -- Retrieve versioning and check for compliance -- Precondition: -- atleast -- string, with required version -- or wikidata|item|~|@ or false -- Postcondition: -- Returns string -- with queried version/item, also if problem -- false -- if appropriate -- 2020-08-17 local since = atleast local last = (since
"@") local link = (since
"wikidata" then local item = Failsafe.item since = false if type(item)
"table" then local seek = Failsafe.serialProperty or "P348" local vsn = entity:formatPropertyValues(seek) if type(vsn)
"string" and vsn.value ~= "" then if last and vsn.value
mw.wikibase.getSitelink(suited) then r = false else r = suited end else r = vsn.value end end end end end end if type(r)
local function Template(frame, action, amount) -- Run actual code from template transclusion -- Precondition: -- frame -- object -- action -- string, with function name -- amount -- number, of args if > 1 -- Postcondition: -- Return string or not local n = amount or 1 local v = local r, s for i = 1, n do s = frame.args[i ] if s then s = mw.text.trim(s) if s ~= "" then v[i ] = s end end end -- for i if v[1 ] then r = URLutil[action ](v[1 ], v[2 ], v[3 ]) end return rend -- Template
local p =
function p.decode(frame) return Template(frame, "decode", 2) or ""endfunction p.encode(frame) return Template(frame, "encode", 2) or ""endfunction p.getAuthority(frame) return Template(frame, "getAuthority") or ""endfunction p.getFragment(frame) local r = Template(frame, "getFragment", 2) if r then r = "#" .. r else r = "" end return rendfunction p.getHost(frame) return Template(frame, "getHost") or ""endfunction p.getLocation(frame) return Template(frame, "getLocation") or ""endfunction p.getNormalized(frame) return Template(frame, "getNormalized") or ""endfunction p.getPath(frame) return Template(frame, "getPath") or ""endfunction p.getPort(frame) return Template(frame, "getPort") or ""endfunction p.getQuery(frame) local r = Template(frame, "getQuery", 3) if r then local key = frame.args[2 ] if key then key = mw.text.trim(key) if key
"table" then since = frame.args[1 ] elseif s
"" then since = false end end return Failsafe.failsafe(since) or ""endfunction p.URLutil return URLutilend
return p