https://en.wikipedia.org/w/index.php?action=history&feed=atom&title=Module%3AURLutil
Module:URLutil - Revision history
2025-05-25T04:08:25Z
Revision history for this page on the wiki
MediaWiki 1.45.0-wmf.2
https://en.wikipedia.org/w/index.php?title=Module:URLutil&diff=1126494969&oldid=prev
ExE Boss: CopiedĀ from de:Modul:URLutil
2022-12-09T17:12:27Z
<p>CopiedĀ from <a href="https://de.wikipedia.org/wiki/Modul:URLutil" class="extiw" title="de:Modul:URLutil">de:Modul:URLutil</a></p>
<p><b>New page</b></p><div>local URLutil = { suite = "URLutil",<br />
serial = "2022-04-05",<br />
item = 10859193 }<br />
--[=[<br />
Utilities for URL etc. on www.<br />
* decode()<br />
* encode()<br />
* getAuthority()<br />
* getFragment()<br />
* getHost()<br />
* getLocation()<br />
* getNormalized()<br />
* getPath()<br />
* getPort()<br />
* getQuery()<br />
* getQueryTable()<br />
* getRelativePath()<br />
* getScheme()<br />
* getSortkey()<br />
* getTLD()<br />
* getTop2domain()<br />
* getTop3domain()<br />
* isAuthority()<br />
* isDomain()<br />
* isDomainExample()<br />
* isDomainInt()<br />
* isHost()<br />
* isHostPathResource()<br />
* isIP()<br />
* isIPlocal()<br />
* isIPv4()<br />
* isIPv6()<br />
* isMailAddress()<br />
* isMailLink()<br />
* isProtocolDialog()<br />
* isProtocolWiki()<br />
* isResourceURL()<br />
* isSuspiciousURL()<br />
* isUnescapedURL()<br />
* isWebURL()<br />
* wikiEscapeURL()<br />
* failsafe()<br />
Only [[dotted decimal]] notation for IPv4 expected.<br />
Does not support dotted hexadecimal, dotted octal, or single-number formats.<br />
IPv6 URL (bracketed) not yet implemented; might need Wikintax escaping anyway.<br />
]=]<br />
local Failsafe = URLutil<br />
<br />
<br />
<br />
local decodeComponentProtect = { F = "\"#%<>[\]^`{|}",<br />
P = "\"#%<>[\]^`{|}/?",<br />
Q = "\"#%<>[\]^`{|}&=+;,",<br />
X = "\"#%<>[\]^`{|}&=+;,/?" }<br />
<br />
<br />
<br />
local decodeComponentEscape = function ( averse, adapt )<br />
return adapt == 20 or adapt == 127 or<br />
decodeComponentProtect[ averse ]:find( string.char( adapt ),<br />
1,<br />
true )<br />
end -- decodeComponentEscape()<br />
<br />
<br />
<br />
local decodeComponentML = function ( ask )<br />
local i = 1<br />
local j, n, s<br />
while ( i ) do<br />
i = ask:find( "&#[xX]%x%x+;", i )<br />
if i then<br />
j = ask:find( ";", i + 3, true )<br />
s = ask:sub( i + 2, j - 1 ):upper()<br />
n = s:byte( 1, 1 )<br />
if n == 88 then<br />
n = tonumber( s:sub( 2 ), 16 )<br />
elseif s:match( "^%d+$" ) then<br />
n = tonumber( s )<br />
else<br />
n = false<br />
end<br />
if n then<br />
if n >= 128 then<br />
s = string.format( "&#%d;", n )<br />
elseif decodeComponentEscape( "X", n ) then<br />
s = string.format( "%%%02X", n )<br />
else<br />
s = string.format( "%c", n )<br />
end<br />
j = j + 1<br />
if i == 1 then<br />
ask = s .. ask:sub( j )<br />
else<br />
ask = string.format( "%s%s%s",<br />
ask:sub( 1, i - 1 ),<br />
s,<br />
ask:sub( j ) )<br />
end<br />
end<br />
i = i + 1<br />
end<br />
end -- while i<br />
return ask<br />
end -- decodeComponentML()<br />
<br />
<br />
<br />
local decodeComponentPercent = function ( ask, averse )<br />
local i = 1<br />
local j, k, m, n<br />
while ( i ) do<br />
i = ask:find( "%%[2-7]%x", i )<br />
if i then<br />
j = i + 1<br />
k = j + 1<br />
n = ask:byte( k, k )<br />
k = k + 1<br />
m = ( n > 96 )<br />
if m then<br />
n = n - 32<br />
m = n<br />
end<br />
if n > 57 then<br />
n = n - 55<br />
else<br />
n = n - 48<br />
end<br />
n = ( ask:byte( j, j ) - 48 ) * 16 + n<br />
if n == 39 and<br />
ask:sub( i + 3, i + 5 ) == "%27" then<br />
j = i + 6<br />
while ( ask:sub( j, j + 2 ) == "%27" ) do<br />
j = j + 3<br />
end -- while "%27"<br />
elseif decodeComponentEscape( averse, n ) then<br />
if m then<br />
ask = string.format( "%s%c%s",<br />
ask:sub( 1, j ),<br />
m,<br />
ask:sub( k ) )<br />
end<br />
elseif i == 1 then<br />
ask = string.format( "%c%s", n, ask:sub( k ) )<br />
else<br />
ask = string.format( "%s%c%s",<br />
ask:sub( 1, i - 1 ),<br />
n,<br />
ask:sub( k ) )<br />
end<br />
i = j<br />
end<br />
end -- while i<br />
return ask<br />
end -- decodeComponentPercent()<br />
<br />
<br />
<br />
local getTopDomain = function ( url, mode )<br />
local r = URLutil.getHost( url )<br />
if r then<br />
local pattern = "[%w%%%-]+%.%a[%w%-]*%a)$"<br />
if mode == 3 then<br />
pattern = "[%w%%%-]+%." .. pattern<br />
end<br />
r = mw.ustring.match( "." .. r, "%.(" .. pattern )<br />
if not r then<br />
r = false<br />
end<br />
else<br />
r = false<br />
end<br />
return r<br />
end -- getTopDomain()<br />
<br />
<br />
<br />
local getHash = function ( url )<br />
local r = url:find( "#", 1, true )<br />
if r then<br />
local i = url:find( "&#", 1, true )<br />
if i then<br />
local s<br />
while ( i ) do<br />
s = url:sub( i + 2 )<br />
if s:match( "^%d+;" ) or s:match( "^x%x+;" ) then<br />
r = url:find( "#", i + 4, true )<br />
if r then<br />
i = url:find( "&#", i + 4, true )<br />
else<br />
i = false<br />
end<br />
else<br />
r = i + 1<br />
i = false<br />
end<br />
end -- while i<br />
end<br />
end<br />
return r<br />
end -- getHash()<br />
<br />
<br />
<br />
URLutil.decode = function ( url, enctype )<br />
local r, s<br />
if type( enctype ) == "string" then<br />
s = mw.text.trim( enctype )<br />
if s == "" then<br />
s = false<br />
else<br />
s = s:upper()<br />
end<br />
end<br />
r = mw.text.encode( mw.uri.decode( url, s ) )<br />
if r:find( "[%[|%]]" ) then<br />
local k<br />
r, k = r:gsub( "%[", "&#91;" )<br />
:gsub( "|", "&#124;" )<br />
:gsub( "%]", "&#93;" )<br />
end<br />
return r<br />
end -- URLutil.decode()<br />
<br />
<br />
<br />
URLutil.encode = function ( url, enctype )<br />
local k, r, s<br />
if type( enctype ) == "string" then<br />
s = mw.text.trim( enctype )<br />
if s == "" then<br />
s = false<br />
else<br />
s = s:upper()<br />
end<br />
end<br />
r = mw.uri.encode( url, s )<br />
k = r:byte( 1, 1 )<br />
if -- k == 35 or -- #<br />
k == 42 or -- *<br />
k == 58 or -- :<br />
k == 59 then -- ;<br />
r = string.format( "%%%X%s", k, r:sub( 2 ) )<br />
end<br />
if r:find( "[%[|%]]" ) then<br />
r, k = r:gsub( "%[", "%5B" )<br />
:gsub( "|", "%7C" )<br />
:gsub( "%]", "%5D" )<br />
end<br />
return r<br />
end -- URLutil.encode()<br />
<br />
<br />
<br />
URLutil.getAuthority = function ( url )<br />
local r<br />
if type( url ) == "string" then<br />
local colon, host, port<br />
local pattern = "^%s*%w*:?//([%w%.%%_-]+)(:?)([%d]*)/"<br />
local s = mw.text.decode( url )<br />
local i = s:find( "#", 6, true )<br />
if i then<br />
s = s:sub( 1, i - 1 ) .. "/"<br />
else<br />
s = s .. "/"<br />
end<br />
host, colon, port = mw.ustring.match( s, pattern )<br />
if URLutil.isHost( host ) then<br />
host = mw.ustring.lower( host )<br />
if colon == ":" then<br />
if port:find( "^[1-9]" ) then<br />
r = ( host .. ":" .. port )<br />
end<br />
elseif #port == 0 then<br />
r = host<br />
end<br />
end<br />
else<br />
r = false<br />
end<br />
return r<br />
end -- URLutil.getAuthority()<br />
<br />
<br />
<br />
URLutil.getFragment = function ( url, decode )<br />
local r<br />
if type( url ) == "string" then<br />
local i = getHash( url )<br />
if i then<br />
r = mw.text.trim( url:sub( i ) ):sub( 2 )<br />
if type( decode ) == "string" then<br />
local encoding = mw.text.trim( decode )<br />
local launch<br />
if encoding == "%" then<br />
launch = true<br />
elseif encoding == "WIKI" then<br />
r = r:gsub( "%.(%x%x)", "%%%1" )<br />
:gsub( "_", " " )<br />
launch = true<br />
end<br />
if launch then<br />
r = mw.uri.decode( r, "PATH" )<br />
end<br />
end<br />
else<br />
r = false<br />
end<br />
else<br />
r = nil<br />
end<br />
return r<br />
end -- URLutil.getFragment()<br />
<br />
<br />
<br />
URLutil.getHost = function ( url )<br />
local r = URLutil.getAuthority( url )<br />
if r then<br />
r = mw.ustring.match( r, "^([%w%.%%_%-]+):?[%d]*$" )<br />
end<br />
return r<br />
end -- URLutil.getHost()<br />
<br />
<br />
<br />
URLutil.getLocation = function ( url )<br />
local r<br />
if type( url ) == "string" then<br />
r = mw.text.trim( url )<br />
if r == "" then<br />
r = false<br />
else<br />
local i<br />
i = getHash( r )<br />
if i then<br />
if i == 1 then<br />
r = false<br />
else<br />
r = r:sub( 1, i - 1 )<br />
end<br />
end<br />
end<br />
else<br />
r = nil<br />
end<br />
return r<br />
end -- URLutil.getLocation()<br />
<br />
<br />
<br />
URLutil.getNormalized = function ( url )<br />
local r<br />
if type( url ) == "string" then<br />
r = mw.text.trim( url )<br />
if r == "" then<br />
r = false<br />
else<br />
r = decodeComponentML( r )<br />
end<br />
else<br />
r = false<br />
end<br />
if r then<br />
local k = r:find( "//", 1, true )<br />
if k then<br />
local j = r:find( "/", k + 2, true )<br />
local sF, sP, sQ<br />
if r:find( "%%[2-7]%x" ) then<br />
local i = getHash( r )<br />
if i then<br />
sF = r:sub( i + 1 )<br />
r = r:sub( 1, i - 1 )<br />
if sF == "" then<br />
sF = false<br />
else<br />
sF = decodeComponentPercent( sF, "F" )<br />
end<br />
end<br />
i = r:find( "?", 1, true )<br />
if i then<br />
sQ = r:sub( i )<br />
r = r:sub( 1, i - 1 )<br />
sQ = decodeComponentPercent( sQ, "Q" )<br />
end<br />
if j then<br />
if #r > j then<br />
sP = r:sub( j + 1 )<br />
sP = decodeComponentPercent( sP, "P" )<br />
end<br />
r = r:sub( 1, j - 1 )<br />
end<br />
elseif j then<br />
local n = #r<br />
if r:byte( n, n ) == 35 then -- '#'<br />
n = n - 1<br />
r = r:sub( 1, n )<br />
end<br />
if n > j then<br />
sP = r:sub( j + 1 )<br />
end<br />
r = r:sub( 1, j - 1 )<br />
end<br />
r = mw.ustring.lower( r ) .. "/"<br />
if sP then<br />
r = r .. sP<br />
end<br />
if sQ then<br />
r = r .. sQ<br />
end<br />
if sF then<br />
r = string.format( "%s#%s", r, sF )<br />
end<br />
end<br />
r = r:gsub( " ", "%%20" )<br />
:gsub( "%[", "%%5B" )<br />
:gsub( "|", "%%7C" )<br />
:gsub( "%]", "%%5D" )<br />
:gsub( "%<", "%%3C" )<br />
:gsub( "%>", "%%3E" )<br />
end<br />
return r<br />
end -- URLutil.getNormalized()<br />
<br />
<br />
<br />
URLutil.getPath = function ( url )<br />
local r = URLutil.getRelativePath( url )<br />
if r then<br />
local s = r:match( "^([^%?]*)%?" )<br />
if s then<br />
r = s<br />
end<br />
s = r:match( "^([^#]*)#" )<br />
if s then<br />
r = s<br />
end<br />
end<br />
return r<br />
end -- URLutil.getPath()<br />
<br />
<br />
<br />
URLutil.getPort = function ( url )<br />
local r = URLutil.getAuthority( url )<br />
if r then<br />
r = r:match( ":([1-9][0-9]*)$" )<br />
if r then<br />
r = tonumber( r )<br />
else<br />
r = false<br />
end<br />
end<br />
return r<br />
end -- URLutil.getPort()<br />
<br />
<br />
<br />
URLutil.getQuery = function ( url, key, separator )<br />
local r = URLutil.getLocation( url )<br />
if r then<br />
r = r:match( "^[^%?]*%?(.+)$" )<br />
if r then<br />
if type( key ) == "string" then<br />
local single = mw.text.trim( key )<br />
local sep = "&"<br />
local s, scan<br />
if type( separator ) == "string" then<br />
s = mw.text.trim( separator )<br />
if s:match( "^[&;,/]$" ) then<br />
sep = s<br />
end<br />
end<br />
s = string.format( "%s%s%s", sep, r, sep )<br />
scan = string.format( "%s%s=([^%s]*)%s",<br />
sep, key, sep, sep )<br />
r = s:match( scan )<br />
end<br />
end<br />
if not r then<br />
r = false<br />
end<br />
end<br />
return r<br />
end -- URLutil.getQuery()<br />
<br />
<br />
<br />
URLutil.getQueryTable = function ( url, separator )<br />
local r = URLutil.getQuery( url )<br />
if r then<br />
local sep = "&"<br />
local n, pairs, s, set<br />
if type( separator ) == "string" then<br />
s = mw.text.trim( separator )<br />
if s:match( "^[&;,/]$" ) then<br />
sep = s<br />
end<br />
end<br />
pairs = mw.text.split( r, sep, true )<br />
n = #pairs<br />
r = { }<br />
for i = 1, n do<br />
s = pairs[ i ]<br />
if s:find( "=", 2, true ) then<br />
s, set = s:match( "^([^=]+)=(.*)$" )<br />
if s then<br />
r[ s ] = set<br />
end<br />
else<br />
r[ s ] = false<br />
end<br />
end -- for i<br />
end<br />
return r<br />
end -- URLutil.getQueryTable()<br />
<br />
<br />
<br />
URLutil.getRelativePath = function ( url )<br />
local r<br />
if type( url ) == "string" then<br />
local s = url:match( "^%s*[a-zA-Z]*://(.*)$" )<br />
if s then<br />
s = s:match( "[^/]+(/.*)$" )<br />
else<br />
local x<br />
x, s = url:match( "^%s*(/?)(/.*)$" )<br />
if x == "/" then<br />
s = s:match( "/[^/]+(/.*)$" )<br />
end<br />
end<br />
if s then<br />
r = mw.text.trim( s )<br />
elseif URLutil.isResourceURL( url ) then<br />
r = "/"<br />
else<br />
r = false<br />
end<br />
else<br />
r = nil<br />
end<br />
return r<br />
end -- URLutil.getRelativePath()<br />
<br />
<br />
<br />
URLutil.getScheme = function ( url )<br />
local r<br />
if type( url ) == "string" then<br />
local pattern = "^%s*([a-zA-Z]*)(:?)(//)"<br />
local prot, colon, slashes = url:match( pattern )<br />
r = false<br />
if slashes == "//" then<br />
if colon == ":" then<br />
if #prot > 2 then<br />
r = prot:lower() .. "://"<br />
end<br />
elseif #prot == 0 then<br />
r = "//"<br />
end<br />
end<br />
else<br />
r = nil<br />
end<br />
return r<br />
end -- URLutil.getScheme()<br />
<br />
<br />
<br />
URLutil.getSortkey = function ( url )<br />
local r = url<br />
if type( url ) == "string" then<br />
local i = url:find( "//" )<br />
if i then<br />
local scheme<br />
if i == 0 then<br />
scheme = ""<br />
else<br />
scheme = url:match( "^%s*([a-zA-Z]*)://" )<br />
end<br />
if scheme then<br />
local s = url:sub( i + 2 )<br />
local comps, site, m, suffix<br />
scheme = scheme:lower()<br />
i = s:find( "/" )<br />
if i and i > 1 then<br />
suffix = s:sub( i + 1 ) -- mw.uri.encode()<br />
s = s:sub( 1, i - 1 )<br />
suffix = suffix:gsub( "#", " " )<br />
else<br />
suffix = ""<br />
end<br />
site, m = s:match( "^(.+)(:%d+)$" )<br />
if not m then<br />
site = s<br />
m = 0<br />
end<br />
comps = mw.text.split( site:lower(), ".", true )<br />
r = "///"<br />
for i = #comps, 2, -1 do<br />
r = string.format( "%s%s.", r, comps[ i ] )<br />
end -- for --i<br />
r = string.format( "%s%s %d %s: %s",<br />
r, comps[ 1 ], m, scheme, suffix )<br />
end<br />
end<br />
end<br />
return r<br />
end -- URLutil.getSortkey()<br />
<br />
<br />
<br />
URLutil.getTLD = function ( url )<br />
local r = URLutil.getHost( url )<br />
if r then<br />
r = mw.ustring.match( r, "%w+%.(%a[%w%-]*%a)$" )<br />
if not r then<br />
r = false<br />
end<br />
end<br />
return r<br />
end -- URLutil.getTLD()<br />
<br />
<br />
<br />
URLutil.getTop2domain = function ( url )<br />
return getTopDomain( url, 2 )<br />
end -- URLutil.getTop2domain()<br />
<br />
<br />
<br />
URLutil.getTop3domain = function ( url )<br />
return getTopDomain( url, 3 )<br />
end -- URLutil.getTop3domain()<br />
<br />
<br />
<br />
URLutil.isAuthority = function ( s )<br />
local r<br />
if type( s ) == "string" then<br />
local pattern = "^%s*([%w%.%%_-]+)(:?)(%d*)%s*$"<br />
local host, colon, port = mw.ustring.match( s, pattern )<br />
if colon == ":" then<br />
port = port:match( "^[1-9][0-9]*$" )<br />
if type( port ) ~= "string" then<br />
r = false<br />
end<br />
elseif port ~= "" then<br />
r = false<br />
end<br />
r = URLutil.isHost( host )<br />
else<br />
r = nil<br />
end<br />
return r<br />
end -- URLutil.isAuthority()<br />
<br />
<br />
<br />
URLutil.isDomain = function ( s )<br />
local r<br />
if type( s ) == "string" then<br />
local scan = "^%s*([%w%.%%_-]*%w)%.(%a[%w-]*%a)%s*$"<br />
local scope<br />
s, scope = mw.ustring.match( s, scan )<br />
if type( s ) == "string" then<br />
if mw.ustring.find( s, "^%w" ) then<br />
if mw.ustring.find( s, "..", 1, true ) then<br />
r = false<br />
else<br />
r = true<br />
end<br />
end<br />
end<br />
else<br />
r = nil<br />
end<br />
return r<br />
end -- URLutil.isDomain()<br />
<br />
<br />
<br />
URLutil.isDomainExample = function ( url )<br />
-- RFC 2606: example.com example.net example.org example.edu<br />
local r = getTopDomain( url, 2 )<br />
if r then<br />
local s = r:lower():match( "^example%.([a-z][a-z][a-z])$" )<br />
if s then<br />
r = ( s == "com" or<br />
s == "edu" or<br />
s == "net" or<br />
s == "org" )<br />
else<br />
r = false<br />
end<br />
end<br />
return r<br />
end -- URLutil.isDomainExample()<br />
<br />
<br />
<br />
URLutil.isDomainInt = function ( url )<br />
-- Internationalized Domain Name (Punycode)<br />
local r = URLutil.getHost( url )<br />
if r then<br />
if r:match( "^[!-~]+$" ) then<br />
local s = "." .. r<br />
if s:find( ".xn--", 1, true ) then<br />
r = true<br />
else<br />
r = false<br />
end<br />
else<br />
r = true<br />
end<br />
end<br />
return r<br />
end -- URLutil.isDomainInt()<br />
<br />
<br />
<br />
URLutil.isHost = function ( s )<br />
return URLutil.isDomain( s ) or URLutil.isIP( s )<br />
end -- URLutil.isHost()<br />
<br />
<br />
<br />
URLutil.isHostPathResource = function ( s )<br />
local r = URLutil.isResourceURL( s )<br />
if not r and s then<br />
r = URLutil.isResourceURL( "//" .. mw.text.trim( s ) )<br />
end<br />
return r<br />
end -- URLutil.isHostPathResource()<br />
<br />
<br />
<br />
URLutil.isIP = function ( s )<br />
return URLutil.isIPv4( s ) and 4 or URLutil.isIPv6( s ) and 6<br />
end -- URLutil.isIP()<br />
<br />
<br />
<br />
URLutil.isIPlocal = function ( s )<br />
-- IPv4 according to RFC 1918, RFC 1122; even any 0.0.0.0 (RFC 5735)<br />
local r = false<br />
local num = s:match( "^ *([01][0-9]*)%." )<br />
if num then<br />
num = tonumber( num )<br />
if num == 0 then<br />
r = s:match( "^ *0+%.[0-9]+%.[0-9]+%.[0-9]+ *$" )<br />
elseif num == 10 or num == 127 then<br />
-- loopback; private/local host: 127.0.0.1<br />
r = URLutil.isIPv4( s )<br />
elseif num == 169 then<br />
-- 169.254.*.*<br />
elseif num == 172 then<br />
-- 172.(16...31).*.*<br />
num = s:match( "^ *0*172%.([0-9]+)%." )<br />
if num then<br />
num = tonumber( num )<br />
if num >= 16 and num <= 31 then<br />
r = URLutil.isIPv4( s )<br />
end<br />
end<br />
elseif beg == 192 then<br />
-- 192.168.*.*<br />
num = s:match( "^ *0*192%.([0-9]+)%." )<br />
if num then<br />
num = tonumber( num )<br />
if num == 168 then<br />
r = URLutil.isIPv4( s )<br />
end<br />
end<br />
end<br />
end<br />
if r then<br />
r = true<br />
end<br />
return r<br />
end -- URLutil.isIPlocal()<br />
<br />
<br />
<br />
URLutil.isIPv4 = function ( s )<br />
local function legal( n )<br />
return ( tonumber( n ) < 256 )<br />
end<br />
local r = false<br />
if type( s ) == "string" then<br />
local p1, p2, p3, p4 = s:match( "^%s*([1-9][0-9]?[0-9]?)%.([12]?[0-9]?[0-9])%.([12]?[0-9]?[0-9])%.([12]?[0-9]?[0-9])%s*$" )<br />
if p1 and p2 and p3 and p4 then<br />
r = legal( p1 ) and legal( p2 ) and legal( p3 ) and legal( p4 )<br />
end<br />
end<br />
return r<br />
end -- URLutil.isIPv4()<br />
<br />
<br />
<br />
URLutil.isIPv6 = function ( s )<br />
local dcolon, groups<br />
if type( s ) ~= "string"<br />
or s:len() == 0<br />
or s:find( "[^:%x]" ) -- only colon and hex digits are legal chars<br />
or s:find( "^:[^:]" ) -- can begin or end with :: but not with single :<br />
or s:find( "[^:]:$" )<br />
or s:find( ":::" )<br />
then<br />
return false<br />
end<br />
s = mw.text.trim( s )<br />
s, dcolon = s:gsub( "::", ":" )<br />
if dcolon > 1 then<br />
return false<br />
end -- at most one ::<br />
s = s:gsub( "^:?", ":" ) -- prepend : if needed, upper<br />
s, groups = s:gsub( ":%x%x?%x?%x?", "" ) -- remove valid groups, and count them<br />
return ( ( dcolon == 1 and groups < 8 ) or<br />
( dcolon == 0 and groups == 8 ) )<br />
and ( s:len() == 0 or ( dcolon == 1 and s == ":" ) ) -- might be one dangling : if original ended with ::<br />
end -- URLutil.isIPv6()<br />
<br />
<br />
<br />
URLutil.isMailAddress = function ( s )<br />
if type( s ) == "string" then<br />
s = mw.ustring.match( s, "^%s*[%w%.%%_-]+@([%w%.%%-]+)%s*$" )<br />
return URLutil.isDomain( s )<br />
end<br />
return false<br />
end -- URLutil.isMailAddress()<br />
<br />
<br />
<br />
URLutil.isMailLink = function ( s )<br />
if type( s ) == "string" then<br />
local addr<br />
s, addr = mw.ustring.match( s, "^%s*([Mm][Aa][Ii][Ll][Tt][Oo]):(%S[%w%.%%_-]*@[%w%.%%-]+)%s*$" )<br />
if type( s ) == "string" then<br />
if s:lower() == "mailto" then<br />
return URLutil.isMailAddress( addr )<br />
end<br />
end<br />
end<br />
return false<br />
end -- URLutil.isMailLink()<br />
<br />
<br />
<br />
local function isProtocolAccepted( prot, supplied )<br />
if type( prot ) == "string" then<br />
local scheme, colon, slashes = mw.ustring.match( prot, "^%s*([a-zA-Z]*)(:?)(/?/?)%s*$" )<br />
if slashes ~= "/" then<br />
if scheme == "" then<br />
if colon ~= ":" and slashes == "//" then<br />
return true<br />
end<br />
elseif colon == ":" or slashes == "" then<br />
local s = supplied:match( " " .. scheme:lower() .. " " )<br />
if type( s ) == "string" then<br />
return true<br />
end<br />
end<br />
end<br />
end<br />
return false<br />
end -- isProtocolAccepted()<br />
<br />
<br />
<br />
URLutil.isProtocolDialog = function ( prot )<br />
return isProtocolAccepted( prot, " mailto irc ircs ssh telnet " )<br />
end -- URLutil.isProtocolDialog()<br />
<br />
<br />
<br />
URLutil.isProtocolWiki = function ( prot )<br />
return isProtocolAccepted( prot,<br />
" ftp ftps git http https nntp sftp svn worldwind " )<br />
end -- URLutil.isProtocolWiki()<br />
<br />
<br />
<br />
URLutil.isResourceURL = function ( url )<br />
local scheme = URLutil.getScheme( url )<br />
if scheme then<br />
local s = " // http:// https:// ftp:// sftp:// "<br />
s = s:find( string.format( " %s ", scheme ) )<br />
if s then<br />
if URLutil.getAuthority( url ) then<br />
if not url:match( "%S%s+%S" ) then<br />
local s1, s2 = url:match( "^([^#]+)(#.*)$" )<br />
if s2 then<br />
if url:match( "^%s*[a-zA-Z]*:?//(.+)/" ) then<br />
return true<br />
end<br />
else<br />
return true<br />
end<br />
end<br />
end<br />
end<br />
end<br />
return false<br />
end -- URLutil.isResourceURL()<br />
<br />
<br />
<br />
URLutil.isSuspiciousURL = function ( url )<br />
if URLutil.isResourceURL( url ) then<br />
local s = URLutil.getAuthority( url )<br />
local pat = "[%[|%]" ..<br />
mw.ustring.char( 34,<br />
8201, 45, 8207,<br />
8234, 45, 8239,<br />
8288 )<br />
.. "]"<br />
if s:find( "@" )<br />
or url:find( "''" )<br />
or url:find( pat )<br />
or url:find( "[%.,]$" ) then<br />
return true<br />
end<br />
-- TODO zero width character ??<br />
return false<br />
end<br />
return true<br />
end -- URLutil.isSuspiciousURL()<br />
<br />
<br />
<br />
URLutil.isUnescapedURL = function ( url, trailing )<br />
if type( trailing ) ~= "string" then<br />
if URLutil.isWebURL( url ) then<br />
if url:match( "[%[|%]]" ) then<br />
return true<br />
end<br />
end<br />
end<br />
return false<br />
end -- URLutil.isUnescapedURL()<br />
<br />
<br />
<br />
URLutil.isWebURL = function ( url )<br />
if URLutil.getScheme( url ) and URLutil.getAuthority( url ) then<br />
if not url:find( "%S%s+%S" ) and<br />
not url:find( "''", 1, true ) then<br />
return true<br />
end<br />
end<br />
return false<br />
end -- URLutil.isWebURL()<br />
<br />
<br />
<br />
URLutil.wikiEscapeURL = function ( url )<br />
if url:find( "[%[|%]]" ) then<br />
local n<br />
url, n = url:gsub( "%[", "&#91;" )<br />
:gsub( "|", "&#124;" )<br />
:gsub( "%]", "&#93;" )<br />
end<br />
return url<br />
end -- URLutil.wikiEscapeURL()<br />
<br />
<br />
<br />
Failsafe.failsafe = function ( atleast )<br />
-- Retrieve versioning and check for compliance<br />
-- Precondition:<br />
-- atleast -- string, with required version<br />
-- or wikidata|item|~|@ or false<br />
-- Postcondition:<br />
-- Returns string -- with queried version/item, also if problem<br />
-- false -- if appropriate<br />
-- 2020-08-17<br />
local since = atleast<br />
local last = ( since == "~" )<br />
local linked = ( since == "@" )<br />
local link = ( since == "item" )<br />
local r<br />
if last or link or linked or since == "wikidata" then<br />
local item = Failsafe.item<br />
since = false<br />
if type( item ) == "number" and item > 0 then<br />
local suited = string.format( "Q%d", item )<br />
if link then<br />
r = suited<br />
else<br />
local entity = mw.wikibase.getEntity( suited )<br />
if type( entity ) == "table" then<br />
local seek = Failsafe.serialProperty or "P348"<br />
local vsn = entity:formatPropertyValues( seek )<br />
if type( vsn ) == "table" and<br />
type( vsn.value ) == "string" and<br />
vsn.value ~= "" then<br />
if last and vsn.value == Failsafe.serial then<br />
r = false<br />
elseif linked then<br />
if mw.title.getCurrentTitle().prefixedText<br />
== mw.wikibase.getSitelink( suited ) then<br />
r = false<br />
else<br />
r = suited<br />
end<br />
else<br />
r = vsn.value<br />
end<br />
end<br />
end<br />
end<br />
end<br />
end<br />
if type( r ) == "nil" then<br />
if not since or since <= Failsafe.serial then<br />
r = Failsafe.serial<br />
else<br />
r = false<br />
end<br />
end<br />
return r<br />
end -- Failsafe.failsafe()<br />
<br />
<br />
<br />
local function Template( frame, action, amount )<br />
-- Run actual code from template transclusion<br />
-- Precondition:<br />
-- frame -- object<br />
-- action -- string, with function name<br />
-- amount -- number, of args if > 1<br />
-- Postcondition:<br />
-- Return string or not<br />
local n = amount or 1<br />
local v = { }<br />
local r, s<br />
for i = 1, n do<br />
s = frame.args[ i ]<br />
if s then<br />
s = mw.text.trim( s )<br />
if s ~= "" then<br />
v[ i ] = s<br />
end<br />
end<br />
end -- for i<br />
if v[ 1 ] then<br />
r = URLutil[ action ]( v[ 1 ], v[ 2 ], v[ 3 ] )<br />
end<br />
return r<br />
end -- Template()<br />
<br />
<br />
<br />
local p = {}<br />
<br />
function p.decode( frame )<br />
return Template( frame, "decode", 2 ) or ""<br />
end<br />
function p.encode( frame )<br />
return Template( frame, "encode", 2 ) or ""<br />
end<br />
function p.getAuthority( frame )<br />
return Template( frame, "getAuthority" ) or ""<br />
end<br />
function p.getFragment( frame )<br />
local r = Template( frame, "getFragment", 2 )<br />
if r then<br />
r = "#" .. r<br />
else<br />
r = ""<br />
end<br />
return r<br />
end<br />
function p.getHost( frame )<br />
return Template( frame, "getHost" ) or ""<br />
end<br />
function p.getLocation( frame )<br />
return Template( frame, "getLocation" ) or ""<br />
end<br />
function p.getNormalized( frame )<br />
return Template( frame, "getNormalized" ) or ""<br />
end<br />
function p.getPath( frame )<br />
return Template( frame, "getPath" ) or ""<br />
end<br />
function p.getPort( frame )<br />
return Template( frame, "getPort" ) or ""<br />
end<br />
function p.getQuery( frame )<br />
local r = Template( frame, "getQuery", 3 )<br />
if r then<br />
local key = frame.args[ 2 ]<br />
if key then<br />
key = mw.text.trim( key )<br />
if key == "" then<br />
key = nil<br />
end<br />
end<br />
if not key then<br />
r = "?" .. r<br />
end<br />
else<br />
r = ""<br />
end<br />
return r<br />
end<br />
function p.getRelativePath( frame )<br />
return Template( frame, "getRelativePath" ) or ""<br />
end<br />
function p.getScheme( frame )<br />
return Template( frame, "getScheme" ) or ""<br />
end<br />
function p.getSortkey( frame )<br />
return Template( frame, "getSortkey" ) or ""<br />
end<br />
function p.getTLD( frame )<br />
return Template( frame, "getTLD" ) or ""<br />
end<br />
function p.getTop2domain( frame )<br />
return Template( frame, "getTop2domain" ) or ""<br />
end<br />
function p.getTop3domain( frame )<br />
return Template( frame, "getTop3domain" ) or ""<br />
end<br />
function p.isAuthority( frame )<br />
return Template( frame, "isAuthority" ) and "1" or ""<br />
end<br />
function p.isDomain( frame )<br />
return Template( frame, "isDomain" ) and "1" or ""<br />
end<br />
function p.isDomainExample( frame )<br />
return Template( frame, "isDomainExample" ) and "1" or ""<br />
end<br />
function p.isDomainInt( frame )<br />
return Template( frame, "isDomainInt" ) and "1" or ""<br />
end<br />
function p.isHost( frame )<br />
return Template( frame, "isHost" ) and "1" or ""<br />
end<br />
function p.isHostPathResource( frame )<br />
return Template( frame, "isHostPathResource" ) and "1" or ""<br />
end<br />
function p.isIP( frame )<br />
return Template( frame, "isIP" ) or ""<br />
end<br />
function p.isIPlocal( frame )<br />
return Template( frame, "isIPlocal" ) and "1" or ""<br />
end<br />
function p.isIPv4( frame )<br />
return Template( frame, "isIPv4" ) and "1" or ""<br />
end<br />
function p.isIPv6( frame )<br />
return Template( frame, "isIPv6" ) and "1" or ""<br />
end<br />
function p.isMailAddress( frame )<br />
return Template( frame, "isMailAddress" ) and "1" or ""<br />
end<br />
function p.isMailLink( frame )<br />
return Template( frame, "isMailLink" ) and "1" or ""<br />
end<br />
function p.isProtocolDialog( frame )<br />
return Template( frame, "isProtocolDialog" ) and "1" or ""<br />
end<br />
function p.isProtocolWiki( frame )<br />
return Template( frame, "isProtocolWiki" ) and "1" or ""<br />
end<br />
function p.isResourceURL( frame )<br />
return Template( frame, "isResourceURL" ) and "1" or ""<br />
end<br />
function p.isSuspiciousURL( frame )<br />
return Template( frame, "isSuspiciousURL" ) and "1" or ""<br />
end<br />
function p.isUnescapedURL( frame )<br />
return Template( frame, "isUnescapedURL", 2 ) and "1" or ""<br />
end<br />
function p.isWebURL( frame )<br />
return Template( frame, "isWebURL" ) and "1" or ""<br />
end<br />
function p.wikiEscapeURL( frame )<br />
return Template( frame, "wikiEscapeURL" )<br />
end<br />
p.failsafe = function ( frame )<br />
local s = type( frame )<br />
local since<br />
if s == "table" then<br />
since = frame.args[ 1 ]<br />
elseif s == "string" then<br />
since = frame<br />
end<br />
if since then<br />
since = mw.text.trim( since )<br />
if since == "" then<br />
since = false<br />
end<br />
end<br />
return Failsafe.failsafe( since ) or ""<br />
end<br />
function p.URLutil()<br />
return URLutil<br />
end<br />
<br />
return p</div>
ExE Boss