Module:Sensitive IP addresses/list/validate

This module validates the data in Module:Sensitive IP addresses/list.

This module validates the data in Module:Sensitive IP addresses/list.



-- 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


Content Disclaimer

Informasi ini disarikan dari Wikipedia dan disajikan kembali untuk tujuan edukasi. Konten tersedia di bawah lisensi CC BY-SA 3.0. Kami tidak bertanggung jawab atas ketidakakuratan data yang bersumber dari kontribusi publik tersebut.

  1. The information displayed on this website is sourced in part or in whole from Wikipedia and has been adapted for the purpose of restating it. We strive to provide accurate and relevant information, however:
  2. There is no guarantee of absolute accuracy. Wikipedia is an open, collaborative project that can be edited by anyone, so information is subject to change.
  3. It is not intended to constitute professional advice. The content displayed is for informational and educational purposes only. For important decisions (e.g., medical, legal, or financial), please consult a professional.
  4. Content copyright. Wikipedia is licensed under the Creative Commons Attribution-ShareAlike License (CC BY-SA). This means that content may be reused with appropriate attribution and shared under a similar license.
  5. Responsible use. Any risk arising from the use of information from this website is entirely the responsibility of the user.