From 93b187ffb96d6d2e5f44eadca3445d3065c0da88 Mon Sep 17 00:00:00 2001 From: knu Date: Tue, 5 Jun 2012 05:04:19 +0000 Subject: merge revision(s) 35865: * lib/ipaddr.rb: Inhibit zero-filled octets in an IPv4 address in all platforms. [ruby-dev:45671] * lib/ipaddr.rb: Allow the x:x:x:x:x:x:d.d.d.d form not limited to IPv4 mapped/compatible addresses. This change also makes it possible for the parser to understand IPv4 mapped and compatible IPv6 addresses in non-compressed form. * lib/ipaddr.rb: Stop exposing IPSocket.valid*? methods which were only usable on non-IPv6-ready platforms. git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/branches/ruby_1_9_3@35917 b2dd03c8-39d4-4d8f-98ff-823fe69b080e --- lib/ipaddr.rb | 222 +++++++++++++++++++++++++++++++++++----------------------- 1 file changed, 136 insertions(+), 86 deletions(-) (limited to 'lib') diff --git a/lib/ipaddr.rb b/lib/ipaddr.rb index b6e7dad918..b6bea19832 100644 --- a/lib/ipaddr.rb +++ b/lib/ipaddr.rb @@ -2,7 +2,7 @@ # ipaddr.rb - A class to manipulate an IP address # # Copyright (c) 2002 Hajimu UMEMOTO . -# Copyright (c) 2007 Akinori MUSHA . +# Copyright (c) 2007, 2009, 2012 Akinori MUSHA . # All rights reserved. # # You can redistribute and/or modify it under the same terms as Ruby. @@ -17,61 +17,6 @@ # require 'socket' -unless Socket.const_defined? "AF_INET6" - class Socket < BasicSocket - # IPv6 protocol family - AF_INET6 = Object.new - end - - class << IPSocket - # Returns +true+ if +addr+ is a valid IPv4 address. - def valid_v4?(addr) - if /\A(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})\Z/ =~ addr - return $~.captures.all? {|i| i.to_i < 256} - end - return false - end - - # Returns +true+ if +addr+ is a valid IPv6 address. - def valid_v6?(addr) - # IPv6 (normal) - return true if /\A[\dA-Fa-f]{1,4}(:[\dA-Fa-f]{1,4})*\Z/ =~ addr - return true if /\A[\dA-Fa-f]{1,4}(:[\dA-Fa-f]{1,4})*::([\dA-Fa-f]{1,4}(:[\dA-Fa-f]{1,4})*)?\Z/ =~ addr - return true if /\A::([\dA-Fa-f]{1,4}(:[\dA-Fa-f]{1,4})*)?\Z/ =~ addr - # IPv6 (IPv4 compat) - return true if /\A[\dA-Fa-f]{1,4}(:[\dA-Fa-f]{1,4})*:/ =~ addr && valid_v4?($') - return true if /\A[\dA-Fa-f]{1,4}(:[\dA-Fa-f]{1,4})*::([\dA-Fa-f]{1,4}(:[\dA-Fa-f]{1,4})*:)?/ =~ addr && valid_v4?($') - return true if /\A::([\dA-Fa-f]{1,4}(:[\dA-Fa-f]{1,4})*:)?/ =~ addr && valid_v4?($') - - false - end - - # Returns +true+ if +addr+ is either a valid IPv4 or IPv6 address. - def valid?(addr) - valid_v4?(addr) || valid_v6?(addr) - end - - alias getaddress_orig getaddress - - # Returns a +String+ based representation of a valid DNS hostname, - # IPv4 or IPv6 address. - # - # IPSocket.getaddress 'localhost' #=> "::1" - # IPSocket.getaddress 'broadcasthost' #=> "255.255.255.255" - # IPSocket.getaddress 'www.ruby-lang.org' #=> "221.186.184.68" - # IPSocket.getaddress 'www.ccc.de' #=> "2a00:1328:e102:ccc0::122" - def getaddress(s) - if valid?(s) - s - elsif /\A[-A-Za-z\d.]+\Z/ =~ s - getaddress_orig(s) - else - raise ArgumentError, "invalid address" - end - end - end -end - # IPAddr provides a set of methods to manipulate an IP address. Both IPv4 and # IPv6 are supported. # @@ -99,9 +44,44 @@ class IPAddr IN4MASK = 0xffffffff # 128 bit mask for IPv4 IN6MASK = 0xffffffffffffffffffffffffffffffff - # Formatstring for IPv6 + # Format string for IPv6 IN6FORMAT = (["%.4x"] * 8).join(':') + # Regexp _internally_ used for parsing IPv4 address. + RE_IPV4ADDRLIKE = %r{ + \A + (\d+) \. (\d+) \. (\d+) \. (\d+) + \z + }x + + # Regexp _internally_ used for parsing IPv6 address. + RE_IPV6ADDRLIKE_FULL = %r{ + \A + (?: + (?: [\da-f]{1,4} : ){7} [\da-f]{1,4} + | + ( (?: [\da-f]{1,4} : ){6} ) + (\d+) \. (\d+) \. (\d+) \. (\d+) + ) + \z + }xi + + # Regexp _internally_ used for parsing IPv6 address. + RE_IPV6ADDRLIKE_COMPRESSED = %r{ + \A + ( (?: (?: [\da-f]{1,4} : )* [\da-f]{1,4} )? ) + :: + ( (?: + ( (?: [\da-f]{1,4} : )* ) + (?: + [\da-f]{1,4} + | + (\d+) \. (\d+) \. (\d+) \. (\d+) + ) + )? ) + \z + }xi + # Returns the address family of this IP address. attr_reader :family @@ -212,7 +192,7 @@ class IPAddr str.gsub!(/\b0{1,3}([\da-f]+)\b/i, '\1') loop do - break if str.sub!(/\A0:0:0:0:0:0:0:0\Z/, '::') + break if str.sub!(/\A0:0:0:0:0:0:0:0\z/, '::') break if str.sub!(/\b0:0:0:0:0:0:0\b/, ':') break if str.sub!(/\b0:0:0:0:0:0\b/, ':') break if str.sub!(/\b0:0:0:0:0\b/, ':') @@ -223,7 +203,7 @@ class IPAddr end str.sub!(/:{3,}/, '::') - if /\A::(ffff:)?([\da-f]{1,4}):([\da-f]{1,4})\Z/i =~ str + if /\A::(ffff:)?([\da-f]{1,4}):([\da-f]{1,4})\z/i =~ str str = sprintf('::%s%d.%d.%d.%d', $1, $2.hex / 256, $2.hex % 256, $3.hex / 256, $3.hex % 256) end @@ -490,11 +470,6 @@ class IPAddr # It seems AI_NUMERICHOST doesn't do the job. #Socket.getaddrinfo(left, nil, Socket::AF_INET6, Socket::SOCK_STREAM, nil, # Socket::AI_NUMERICHOST) - begin - IPSocket.getaddress(prefix) # test if address is valid - rescue - raise ArgumentError, "invalid address" - end @addr = @family = nil if family == Socket::AF_UNSPEC || family == Socket::AF_INET @addr = in_addr(prefix) @@ -528,26 +503,44 @@ class IPAddr end def in_addr(addr) - if addr =~ /^\d+\.\d+\.\d+\.\d+$/ - return addr.split('.').inject(0) { |i, s| - i << 8 | s.to_i - } + case addr + when Array + octets = addr + else + m = RE_IPV4ADDRLIKE.match(addr) or return nil + octets = m.captures end - return nil + octets.inject(0) { |i, s| + (n = s.to_i) < 256 or raise ArgumentError, "invalid address" + s.match(/\A0./) and raise ArgumentError, "zero-filled number is ambiguous" + i << 8 | n + } end def in6_addr(left) case left - when /^::ffff:(\d+\.\d+\.\d+\.\d+)$/i - return in_addr($1) + 0xffff00000000 - when /^::(\d+\.\d+\.\d+\.\d+)$/i - return in_addr($1) - when /[^0-9a-f:]/i - raise ArgumentError, "invalid address" - when /^(.*)::(.*)$/ - left, right = $1, $2 - else + when RE_IPV6ADDRLIKE_FULL + if $2 + addr = in_addr($~[2,4]) + left = $1 + ':' + else + addr = 0 + end right = '' + when RE_IPV6ADDRLIKE_COMPRESSED + if $4 + left.count(':') <= 6 or raise ArgumentError, "invalid address" + addr = in_addr($~[4,4]) + left = $1 + right = $3 + '0:0' + else + left.count(':') <= 7 or raise ArgumentError, "invalid address" + left = $1 + right = $2 + addr = 0 + end + else + raise ArgumentError, "invalid address" end l = left.split(':') r = right.split(':') @@ -555,9 +548,9 @@ class IPAddr if rest < 0 return nil end - return (l + Array.new(rest, '0') + r).inject(0) { |i, s| + (l + Array.new(rest, '0') + r).inject(0) { |i, s| i << 16 | s.hex - } + } | addr end def addr_mask(addr) @@ -599,6 +592,55 @@ class IPAddr end +unless Socket.const_defined? "AF_INET6" + class Socket < BasicSocket + # IPv6 protocol family + AF_INET6 = Object.new + end + + class << IPSocket + private + + def valid_v6?(addr) + case addr + when IPAddr::RE_IPV6ADDRLIKE_FULL + if $2 + $~[2,4].all? {|i| i.to_i < 256 } + else + true + end + when IPAddr::RE_IPV6ADDRLIKE_COMPRESSED + if $4 + addr.count(':') <= 6 && $~[4,4].all? {|i| i.to_i < 256} + else + addr.count(':') <= 7 + end + else + false + end + end + + alias getaddress_orig getaddress + + public + + # Returns a +String+ based representation of a valid DNS hostname, + # IPv4 or IPv6 address. + # + # IPSocket.getaddress 'localhost' #=> "::1" + # IPSocket.getaddress 'broadcasthost' #=> "255.255.255.255" + # IPSocket.getaddress 'www.ruby-lang.org' #=> "221.186.184.68" + # IPSocket.getaddress 'www.ccc.de' #=> "2a00:1328:e102:ccc0::122" + def getaddress(s) + if valid_v6?(s) + s + else + getaddress_orig(s) + end + end + end +end + if $0 == __FILE__ eval DATA.read, nil, $0, __LINE__+4 end @@ -609,10 +651,15 @@ require 'test/unit' class TC_IPAddr < Test::Unit::TestCase def test_s_new - assert_nothing_raised { - IPAddr.new("3FFE:505:ffff::/48") - IPAddr.new("0:0:0:1::") - IPAddr.new("2001:200:300::/48") + [ + ["3FFE:505:ffff::/48"], + ["0:0:0:1::"], + ["2001:200:300::/48"], + ["2001:200:300::192.168.1.2/48"], + ].each { |args| + assert_nothing_raised { + IPAddr.new(*args) + } } a = IPAddr.new @@ -667,9 +714,10 @@ class TC_IPAddr < Test::Unit::TestCase assert_equal("2001:200:300::", IPAddr.new("[2001:200:300::]/48").to_s) [ + ["192.168.0.256"], + ["192.168.0.011"], ["fe80::1%fxp0"], ["::1/255.255.255.0"], - ["::1:192.168.1.2/120"], [IPAddr.new("::1").to_i], ["::ffff:192.168.1.2/120", Socket::AF_INET], ["[192.168.1.2]/120"], @@ -811,7 +859,9 @@ class TC_Operator < Test::Unit::TestCase end def test_equal - assert_equal(true, @a == IPAddr.new("3ffe:505:2::")) + assert_equal(true, @a == IPAddr.new("3FFE:505:2::")) + assert_equal(true, @a == IPAddr.new("3ffe:0505:0002::")) + assert_equal(true, @a == IPAddr.new("3ffe:0505:0002:0:0:0:0:0")) assert_equal(false, @a == IPAddr.new("3ffe:505:3::")) assert_equal(true, @a != IPAddr.new("3ffe:505:3::")) assert_equal(false, @a != IPAddr.new("3ffe:505:2::")) -- cgit v1.2.3