モジュール:IP

-- IP library
-- This library contains classes for working with IP addresses and IP ranges.
-- Load modules
require('strict')
local bit32 = require('bit32')
local libraryUtil = require('libraryUtil')
local checkType = libraryUtil.checkType
local checkTypeMulti = libraryUtil.checkTypeMulti
local makeCheckSelfFunction = libraryUtil.makeCheckSelfFunction
-- Constants
local V4 = 'IPv4'
local V6 = 'IPv6'
--------------------------------------------------------------------------------
-- Helper functions
--------------------------------------------------------------------------------
local function makeValidationFunction(className, isObjectFunc)
	-- Make a function for validating a specific object.
	return function (methodName, argIdx, arg)
		if not isObjectFunc(arg) then
			error(string.format(
				"bad argument #%d to '%s' (not a valid %s object)",
				argIdx, methodName, className
			), 3)
		end
	end
end
--------------------------------------------------------------------------------
-- Collection class
-- This is a table used to hold items.
--------------------------------------------------------------------------------
local Collection = {}
Collection.__index = Collection
function Collection:add(item)
	if item ~= nil then
		self.n = self.n + 1
		selfself.n = item
	end
end
function Collection:join(sep)
	return table.concat(self, sep)
end
function Collection:remove(pos)
	if self.n > 0 and (pos == nil or (0  0 and s:match('^0') then
						-- A redundant leading zero is for an IP in octal.
						return nil
					end
					octetsi = num
				else
					return nil
				end
			else
				return nil
			end
		end
		local parts = Collection.new()
		for i = 1, 3, 2 do
			parts:add(octetsi * 256 + octetsi+1)
		end
		return setmetatable(parts, RawIP)
	end
	return nil
end
function RawIP.newFromIPv6(ipStr)
	-- Return a RawIP object if ipStr is a valid IPv6 string. Otherwise,
	-- return nil.
	ipStr = ipStr:match('^%s*(.-)%s*$')
	local _, n = ipStr:gsub(':', ':')
	if n = 0x10000 then
			carry = previous and 0x10000 or 1
			sum = sum - 0x10000
		else
			carry = previous and 0xffff or 0
		end
		resulti = sum
	end
	return setmetatable(result, RawIP)
end
function RawIP:getPrefix(bitLength)
	-- Return a RawIP object for the prefix of the current IP Address with a
	-- bit length of bitLength.
	local result = Collection.new()
	result.n = self.n
	for i = 1, self.n do
		if bitLength > 0 then
			if bitLength >= 16 then
				resulti = selfi
				bitLength = bitLength - 16
			else
				resulti = bit32.replace(selfi, 0, 0, 16 - bitLength)
				bitLength = 0
			end
		else
			resulti = 0
		end
	end
	return setmetatable(result, RawIP)
end
function RawIP:getHighestHost(bitLength)
	-- Return a RawIP object for the highest IP with the prefix of length
	-- bitLength. In other words, the network (the most-significant bits)
	-- is the same as the current IP's, but the host bits (the
	-- least-significant bits) are all set to 1.
	local bits = self.n * 16
	local width
	if bitLength = bits then
		width = 0
	else
		width = bits - bitLength
	end
	local result = Collection.new()
	result.n = self.n
	for i = self.n, 1, -1 do
		if width > 0 then
			if width >= 16 then
				resulti = 0xffff
				width = width - 16
			else
				resulti = bit32.replace(selfi, 0xffff, 0, width)
				width = 0
			end
		else
			resulti = selfi
		end
	end
	return setmetatable(result, RawIP)
end
function RawIP:_makeIPv6String()
	-- Return an IPv6 string representation of the object. Behavior is
	-- undefined if the current object is IPv4.
	local z1, z2  -- indices of run of zeroes to be displayed as "::"
	local zstart, zcount
	for i = 1, 9 do
		-- Find left-most occurrence of longest run of two or more zeroes.
		if i  1 then
				if not z1 or zcount > z2 - z1 + 1 then
					z1 = zstart
					z2 = zstart + zcount - 1
				end
			end
			zstart = nil
			zcount = nil
		end
	end
	local parts = Collection.new()
	for i = 1, 8 do
		if z1 and z1  data.rawIP:getBitLength()
				or bitLength ~= math.floor(bitLength)
			then
				error(string.format(
					"bad argument #1 to 'getSubnet' (must be an integer between 0 and %d)",
					data.rawIP:getBitLength()
				), 2)
			end
			return makeSubnetFromRaw(data.rawIP, bitLength)
		end
		function obj:getNextIP()
			checkSelf(self, 'getNextIP')
			return makeIPAddressFromRaw(data.rawIP:getAdjacent())
		end
		function obj:getPreviousIP()
			checkSelf(self, 'getPreviousIP')
			return makeIPAddressFromRaw(data.rawIP:getAdjacent(true))
		end
		-- Metamethods
		return setmetatable(obj, {
			__eq = ipEquals,
			__lt = ipLessThan,
			__concat = concatIP,
			__tostring = ipToString,
			__index = function (self, key)
				-- If any code knows the unique data key, allow it to access
				-- the data table.
				if key == dataKey then
					return data
				end
			end,
			__metatable = false, -- don't allow access to the metatable
		})
	end
	makeIPAddress = function (ip)
		local rawIP = RawIP.newFromIP(ip)
		if not rawIP then
			error(string.format("'%s' is an invalid IP address", ip), 3)
		end
		return makeIPAddressFromRaw(rawIP)
	end
	function IPAddress.new(ip)
		checkType('IPAddress.new', 1, ip, 'string')
		return makeIPAddress(ip)
	end
end
--------------------------------------------------------------------------------
-- Subnet class
-- Represents a block of IPv4 or IPv6 addresses.
--------------------------------------------------------------------------------
local Subnet = {}
do
	-- uniqueKey is a unique, private key used to test whether a given object
	-- is a Subnet object.
	local uniqueKey = {}
	-- Metatable
	local mt = {
		__index = function (self, key)
			if key == uniqueKey then
				return true
			end
		end,
		__eq = function (self, obj)
			return self:getCIDR() == obj:getCIDR()
		end,
		__concat = function (self, obj)
			return tostring(self) .. tostring(obj)
		end,
		__tostring = function (self)
			return self:getCIDR()
		end,
		__metatable = false
	}
	-- Private static methods
	local function isSubnetObject(val)
		-- Return true if val is a Subnet object, and false otherwise.
		return type(val) == 'table' and valuniqueKey ~= nil
	end
	-- Function to validate subnet objects.
	-- Params:
	-- methodName (string) - the name of the method being validated
	-- argIdx (number) - the position of the argument in the argument list
	-- arg - the argument to be validated
	validateSubnet = makeValidationFunction('Subnet', isSubnetObject)
	-- Constructors
	makeSubnetFromRaw = function (rawIP, bitLength)
		-- Set up structure
		local obj = setmetatable({}, mt)
		local data = {
			rawIP = rawIP,
			bitLength = bitLength,
		}
		-- A function to check whether methods are called with a valid self
		-- parameter.
		local checkSelf = makeCheckSelfFunction(
			'IP',
			'subnet',
			obj,
			'Subnet object'
		)
		-- Public methods
		function obj:getPrefix()
			checkSelf(self, 'getPrefix')
			if not data.prefix then
				data.prefix = makeIPAddressFromRaw(
					data.rawIP:getPrefix(data.bitLength)
				)
			end
			return data.prefix
		end
		function obj:getHighestIP()
			checkSelf(self, 'getHighestIP')
			if not data.highestIP then
				data.highestIP = makeIPAddressFromRaw(
					data.rawIP:getHighestHost(data.bitLength)
				)
			end
			return data.highestIP
		end
		function obj:getBitLength()
			checkSelf(self, 'getBitLength')
			return data.bitLength
		end
		function obj:getCIDR()
			checkSelf(self, 'getCIDR')
			return string.format(
				'%s/%d',
				tostring(self:getPrefix()), self:getBitLength()
			)
		end
		function obj:getVersion()
			checkSelf(self, 'getVersion')
			return data.rawIP:getVersion()
		end
		function obj:isIPv4()
			checkSelf(self, 'isIPv4')
			return data.rawIP:isIPv4()
		end
		function obj:isIPv6()
			checkSelf(self, 'isIPv6')
			return data.rawIP:isIPv6()
		end
		function obj:containsIP(ip)
			checkSelf(self, 'containsIP')
			local tp = type(ip)
			if tp == 'string' then
				ip = makeIPAddress(ip)
			elseif tp == 'table' then
				validateIPAddress('containsIP', 1, ip)
			else
				checkTypeMulti('containsIP', 1, ip, {'string', 'table'})
			end
			if self:getVersion() == ip:getVersion() then
				return self:getPrefix() = self:getPrefix() and
					subnet:getPrefix()  ip2 then
			error('The first IP must be less than or equal to the second', 2)
		end
	end
	Collection.add(self, {ip1, ip2 or ip1})
end
function Ranges:merge()
	self:sort(
		function (lhs, rhs)
			-- Sort by second value, then first.
			if lhs2 == rhs2 then
				return lhs1  1 do
		for i = pos - 1, 1, -1 do
			local ip1 = selfi2
			local ip2 = ip1:getNextIP()
			if ip2  ip2 then
				break
			end
			ip1 = selfi1
			ip2 = selfpos1
			selfi = {ip1 > ip2 and ip2 or ip1, selfpos2}
			self:remove(pos)
			pos = pos - 1
			if pos ?%_', ' ')
			for position, hit in line:gmatch('()(%S+)') do
				local ip = hit:match('^(:%x+)/?%d*$')
				if ip then
					local _, n = ip:gsub(':', ':')
					if n >= 2 then
						self:_store(hit, position == 1)
					end
				end
			end
		end
		return self
	end
end
--------------------------------------------------------------------------------
-- Util class (static)
-- Holds utility functions.
--------------------------------------------------------------------------------
local Util = {}
function Util.removeDirMarkers(str)
-- Remove any of following directional markers
	-- LRM : LEFT-TO-RIGHT MARK (U+200E)         : hex e2 80 8e = 226 128 142
	-- LRE : LEFT-TO-RIGHT EMBEDDING (U+202A)    : hex e2 80 aa = 226 128 170
	-- PDF : POP DIRECTIONAL FORMATTING (U+202C) : hex e2 80 ac = 226 128 172
	-- This is required for MediaWiki:Blockedtext message.
	return string.gsub(str, '\226\128\142\170\172', )
end
local function correctCidr(cidrStr)
-- Correct a well-formatted but invalid CIDR string to a valid one (e.g. 255.255.255.1/24 -> 255.255.255.0/24).
-- Return a Subnet object only if correction takes place.
	local isCidr, cidr = pcall(Subnet.new, cidrStr)
local i, _ = string.find(cidrStr, '/%d+$');
if not isCidr and i ~= nil and i > 1 then
local bitLen = tonumber(cidrStr:sub(i + 1))
local root = cidrStr:sub(1, i - 1)
local isIp, ip = pcall(IPAddress.new, root)
if isIp then
local isValidSubnet = ip:isIPv4() and 0  1.2.3.0/24).
	str = Util.removeDirMarkers(str)
	if cidrOnly == true then allowCidr = true end -- Ignores the value of allowCidr if cidrOnly is true
	if allowCidr then
		local corCidr = correctCidr(str)
		local corrected = corCidr ~= nil
		local isCidr, cidr
		if corrected then
			isCidr, cidr = true, corCidr
		else
			isCidr, cidr = pcall(Subnet.new, str)
		end
if isCidr then -- The input (or corrected) string represents a valid CIDR
			isCidr = isSpecifiedProtocol(cidr, protocol)
			return isCidr, str, (function() if isCidr and corrected then return cidr:getCIDR() end end)()
		elseif cidrOnly then -- Invalid as a CIDR
			return false, str, nil
		end
end
local isIp, ip = pcall(IPAddress.new, str)
	if isIp then
		isIp = isSpecifiedProtocol(ip, protocol)
	end
return isIp, str, nil
end
function Util.isIPAddress(str, allowCidr, cidrOnly)
	return verifyIP(str, allowCidr, cidrOnly, nil)
end
function Util.isIPv4Address(str, allowCidr, cidrOnly)
	return verifyIP(str, allowCidr, cidrOnly, 'v4')
end
function Util.isIPv6Address(str, allowCidr, cidrOnly)
	return verifyIP(str, allowCidr, cidrOnly, 'v6')
end
return {
	IPAddress = IPAddress,
	Subnet = Subnet,
	IPv4Collection = IPv4Collection,
	IPv6Collection = IPv6Collection,
Util = Util
}