Module:Sensitive IP addresses/list/validate
MyWikiBiz, Author Your Legacy — Thursday December 05, 2024
Jump to navigationJump to search-- This module validates the data in [[Module:Sensitive IP addresses/list]]. -- Load modules local mSIPA_API = require('Module:Sensitive IP addresses/API') local Subnet = require('Module:IP').Subnet -- Constants local DATA_MODULE = 'Module:Sensitive IP addresses/list' local p = {} local function makeErrorLogger() -- Return an object for formatting errors. return { errors = {}, addError = function (self, msg, ...) table.insert(self.errors, string.format(msg, ...)) end, addEntryTypeError = function (self, entryIdx, field, actual, expected) self:addError( 'The %s field in data entry #%d was type %s (should be string or nil)', field, entryIdx, actual, expected ) end, hasErrors = function (self) return #self.errors > 0 end, makeReport = function (self) if #self.errors < 1 then return 'No errors found' else local ret = {'Found the following errors:'} for i, msg in ipairs(self.errors) do ret[#ret + 1] = string.format('* <strong class="error">%s</strong>', msg) end return table.concat(ret, '\n') end end, } 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 [[mw:LUAREF#mw.loadData|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 data end 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{'name', 'id', 'description'} 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{'notes'} 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{'ipv4Ranges', 'ipv6Ranges'} 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 end end 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 end end 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 end end 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 = { ipv4 = {}, ipv6 = {}, } for dataIndex, subtable in ipairs(data) do for i, field in ipairs{'ipv4Ranges', 'ipv6Ranges'} 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], { dataIndex = dataIndex, field = field, rangeIndex = j, subnet = subnet, name = subtable.name, }) 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:makeReport() end return p