Module:Sensitive IP addresses/list/validate explained

-- This module validates the data in .

-- Load moduleslocal mSIPA_API = require('Module:Sensitive IP addresses/API')local Subnet = require('Module:IP').Subnet

-- Constantslocal DATA_MODULE = 'Module:Sensitive IP addresses/list'

local p =

local function makeErrorLogger -- Return an object for formatting errors. return end

local function loadData(logger) -- Load the data table, logging any errors in the process.

-- Check whether the data module can be successfully loaded. local success, data = pcall(mw.loadData, DATA_MODULE) if not success then logger:addError('%s could not be parsed by mw.loadData; check for invalid data', DATA_MODULE) return nil end

-- Check that the data table is a table. if type(data) ~= 'table' then logger:addError('%s returned a %s; table expected', DATA_MODULE, type(data)) end

return dataend

local function checkDataStructure(logger, data) -- Check the structure of the individual entries in the data table. for dataIndex, subtable in ipairs(data) do -- Check that subtables are tables. if type(subtable) ~= 'table' then logger:addError('Data entry #%d is not a table', dataIndex) end

-- Check that we have required string fields. for _, field in ipairs do if type(subtable[field]) ~= 'string' then logger:addError("Missing field '%s' in data entry #%d", field, dataIndex ) elseif subtable[field]

then logger:addError("Blank field '%s' in data entry #%d", field, dataIndex ) end end

-- Check that optional string fields are strings if they are present. for _, field in ipairs do local val = subtable[field] if val ~= nil and type(val) ~= 'string' then logger:addEntryTypeError(dataIndex, field, type(val), 'string or nil') end end

-- Check that the reason is valid if it is present. if subtable.reason ~= nil then if type(subtable.reason) ~= 'string' then logger:addEntryTypeError(dataIndex, 'reason', type(subtable.reason), 'string or nil' ) elseif not mSIPA_API._isValidSensitivityReason(subtable.reason) then logger:addError("The reason field in data entry #%d was invalid (should be '%s')", dataIndex, mSIPA_API._getSensitivityReasons("', '", "', or '") ) end end

-- Check IP range tables. for i, field in ipairs do local ranges = subtable[field] if ranges ~= nil then if type(ranges) ~= 'table' then logger:addEntryTypeError(dataIndex, field, type(ranges), 'table or nil') else for j, range in ipairs(ranges) do if type(range) ~= 'string' then logger:addError('Range #%d in the %s field of entry #%d was type %s (expected string)', j, field, type(range) ) elseif range

then logger:addError('Range #%d in the %s field of entry #%d was a blank string', j, field ) end end end end end endend

local function makeSubnet(cidr) -- Make a subnet object from a CIDR string. Returns a subnet object, or nil -- if there were any errors. local success, obj = pcall(Subnet.new, cidr) if success then return obj endend

local function checkDuplicateIds(logger, data) -- Check that there are no duplicate IDs in the data. local ids = for dataIndex, subtable in ipairs(data) do if ids[subtable.id] then logger:addError("Data entry #%d (%s) and data entry #%d (%s) have duplicate ID '%s'", ids[subtable.id], data[ids[subtable.id]].name, dataIndex, subtable.name, subtable.id ) else ids[subtable.id] = dataIndex end endend

local function checkRanges(logger, data) -- Check the ranges in the data table to make sure they are all valid and -- that they don't overlap with each other. This function assumes that the -- structure of the data table is valid.

-- Make an array of subnet data for easy comparison local ranges = for dataIndex, subtable in ipairs(data) do for i, field in ipairs do local cidrs = subtable[field] if cidrs then for j, cidr in ipairs(cidrs) do local subnet = makeSubnet(cidr) if subnet then local ipVersion = field

'ipv4Ranges' and 'IPv4' or 'IPv6' local rangeKey = ipVersion:lower if ipVersion

subnet:getVersion then table.insert(ranges[rangeKey],) else logger:addError("Found %s CIDR string '%s' in range #%d in the %s field of entry #%d (%s); should be %s", subnet:getVersion, cidr, j, field, dataIndex, subtable.name, ipVersion ) end else logger:addError("Invalid CIDR string '%s' in range #%d in the %s field of entry #%d (%s)", cidr, j, field, dataIndex, subtable.name ) end end end end end -- Check for overlapping subnets local nComparisons = 0 for ipVersion, versionData in pairs(ranges) do local lim = #versionData for i = 1, lim - 1 do local subnetData1 = versionData[i] for j = i + 1, lim do local subnetData2 = versionData[j] nComparisons = nComparisons + 1 if subnetData1.subnet:overlapsSubnet(subnetData2.subnet) then logger:addError("%s range #%d '%s' in data entry #%d (%s) overlaps range #%d '%s' in data entry #%d (%s)", ipVersion

'ipv4' and 'IPv4' or 'IPv6', subnetData1.rangeIndex, subnetData1.subnet:getCIDR, subnetData1.dataIndex, subnetData1.name, subnetData2.rangeIndex, subnetData2.subnet:getCIDR, subnetData2.dataIndex, subnetData2.name ) end end end end mw.log(nComparisons .. ' subnet comparisons performed')end

function p.main local logger = makeErrorLogger local data = loadData(logger) if logger:hasErrors then return logger:makeReport end checkDataStructure(logger, data) if logger:hasErrors then return logger:makeReport end checkDuplicateIds(logger, data) checkRanges(logger, data) return logger:makeReportend

return p