Module:Sensitive IP addresses/API: Difference between revisions

Content deleted Content added
only include query result subtables when we have data, allow returning queries as JSON, and allow checking for the reason when making the summary table
m Protected "Module:Sensitive IP addresses/API": High-risk Lua module: used in the MediaWiki interface, e.g. MediaWiki:Blockiptext via Template:Sensitive IP addresses ([Edit=Require administrator access] (indefinite...
 
(12 intermediate revisions by the same user not shown)
Line 7:
local IPv4Collection = mIP.IPv4Collection
local IPv6Collection = mIP.IPv6Collection
 
local libraryUtil = require('libraryUtil')
-- Lazily load the jf-JSON module
local checkType = libraryUtil.checkType
local JSON
 
-------------------------------------------------------------------------------
Line 26 ⟶ 27:
else
return val
end
end
 
local function deepCopyInto(source, dest)
-- Do a deep copy of a source table into a destination table, ignoring
-- self-references and metatables. If a table in source has a self-reference
-- you will get an infinite loop.
for k, v in pairs(source) do
if type(v) == 'table' then
dest[k] = {}
deepCopyInto(v, dest[k])
else
dest[k] = v
end
end
end
Line 87 ⟶ 102:
-- otherwise. matchObj is the Subnet object that was matched, and queryObj
-- is the IPAddress or Subnet object corresponding to the input string.
checkType('matchesIPOrRange', 1, str, 'string')
 
-- Get the IPAddress or Subnet object for str
Line 156 ⟶ 170:
-- {
-- entities = {'all'}
-- }
--
-- Query all entities and format the result as a JSON string:
-- {
-- entities = {'all'},
-- format = 'json'
-- }
--
Line 237 ⟶ 257:
end
 
local function makeError(code, info, format)
returnlocal ret = {['error'] = {
code = code,
info = info,
['*'] = 'See https://en.wikipedia.org/wiki/Module:Sensitive_IP_addresses/API for API usage',
}}
if format == 'json' then
return mw.text.jsonEncode(ret)
else
return ret
end
end
 
-- Construct result
local result = {}
matches = {},
['matched-ranges'] = {},
entities = {},
['entity-ids'] = {}
}
 
if type(options) ~= 'table' then
Line 259 ⟶ 289:
return makeError(
'sipa-blank-options',
"the options table didn't contain a 'test' or an 'entities' key",
options.format
)
end
Line 270 ⟶ 301:
"'test' options key was type %s (expected table)",
type(options.test)
),
options.format
)
end
Line 282 ⟶ 314:
i,
type(testString)
),
options.format
)
end
Line 301 ⟶ 334:
i,
testString
),
options.format
)
end
if isMatch then
-- The string was a sensitive IP address or subnet.
 
-- Set up result subtables
result.matches = result.matches or {}
result['matched-ranges'] = result['matched-ranges'] or {}
result.entities = result.entities or {}
result['entity-ids'] = result['entity-ids'] or {}
 
-- Add match data
Line 356 ⟶ 384:
"'entities' options key was type %s (expected table)",
type(options.test)
),
options.format
)
end
Line 371 ⟶ 400:
i,
type(entityString)
),
options.format
)
end
Line 394 ⟶ 424:
-- Insert the entity and entity-id subtables if they aren't already
-- present.
result.entities = result.entities or {}
result['entity-ids'] = result['entity-ids'] or {}
for i, entityString in ipairs(options.entities) do
if entities[entityString] then
Line 412 ⟶ 440:
 
-- Add any missing reason fields from entities.
iffor id, entityData in pairs(result.entities) thendo
for id, entityData.reason in= pairs(resultentityData.entities)reason or do'political'
entityData.reason = entityData.reason or 'political'
end
end
 
Line 422 ⟶ 448:
 
if options.format == 'json' then
-- Load jf-JSON
return mw.text.jsonEncode(result)
JSON = JSON or require('Module:jf-JSON')
JSON.strictTypes = true -- Necessary for correct blank-object encoding
-- Decode a skeleton result JSON string. This ensures that blank objects
-- are re-encoded as blank objects and not as blank arrays.
local jsonResult = JSON:decode([[{"sensitiveips": {
"matches": [],
"matched-ranges": {},
"entities": {},
"entity-ids": []
}}]])
for i, key in ipairs{'matches', 'matched-ranges', 'entities', 'entity-ids'} do
deepCopyInto(result.sensitiveips[key], jsonResult.sensitiveips[key])
end
return JSON:encode(jsonResult)
elseif options.format == nil or options.format == 'lua' then
return result
elseif type(options.format) ~= 'string' then
return makeError(
'sipa-format-type-error',
string.format(
"'format' options key was type %s (expected string or nil)",
type(options.format)
)
)
else
return resultmakeError(
'sipa-invalid-format',
string.format(
"invalid format '%s' (expected 'json' or 'lua')",
type(options.format)
)
)
end
end
 
-------------------------------------------------------------------------------
-- Summary table
-- A table of sensitive IP data to be used in
-- [[Template:Sensitive IP addresses]].
-------------------------------------------------------------------------------
 
local function makeSummaryTable(options)
options = options or {}
local data = query{entities={'all'}}
if data['error'] then
error(string.format('%s: %s', data['error'].code, data['error'].info))
end
local root = mw.html.create('table')
root
:addClass('wikitable')
:addClass('sortable')
 
-- Add headers
root:tag('tr')
:tag('th')
:wikitext('[[IPv4]]')
:done()
:tag('th')
:wikitext('[[IPv6]]')
:done()
:tag('th')
:wikitext('Description')
 
for i, id in ipairs(data.sensitiveips['entity-ids']) do
local entityData = data.sensitiveips.entities[id]
if not options.reason or options.reason == entityData.reason then
root:tag('tr')
:tag('td')
:wikitext(entityData.ipv4Ranges
and table.concat(entityData.ipv4Ranges, ', ')
or nil
)
:done()
:tag('td')
:wikitext(entityData.ipv6Ranges
and table.concat(entityData.ipv6Ranges, ', ')
or nil
)
:done()
:tag('td')
:wikitext(entityData.description or entityData.name)
end
end
 
return tostring(root)
end
 
Line 486 ⟶ 490:
local p = {}
 
function p.isValidSensitivityReason_isValidSensitivityReason(s)
-- Return true if s is a valid sensitivity reason; otherwise return false.
return s ~= nil and SensitiveEntity.reasons[s] ~= nil
checkType('isValidSensitivityReason', 1, s, 'string')
return SensitiveEntity.reasons[s] ~= nil
end
 
function p.getSensitivityReasons_getSensitivityReasons(separator, conjunction)
-- Return an arraystring of valid sensitivity reasons, ordered alphabetically.
-- The reasons are separated by an optional separator; if conjunction is
local ret = {}
-- specified it is used instead of the last separator, as in
-- mw.text.listToText.
 
-- Get an array of valid sensitivity reasons.
local reasons = {}
for reason in pairs(SensitiveEntity.reasons) do
retreasons[#retreasons + 1] = reason
end
table.sort(retreasons)
 
return ret
-- Convert arguments if we are being called from wikitext.
if type(separator) == 'table' and type(separator.getParent) == 'function' then
-- separator is a frame object
local frame = separator
separator = frame.args[1]
conjunction = frame.args[2]
end
 
-- Return a formatted string
return mw.text.listToText(reasons, separator, conjunction)
end
 
-- Export the API query function
p.query = query
 
-- Export the summary table function
p.summary = makeSummaryTable
 
return p