* lib/ipaddr.rb: Inhibit zero-filled octets in an IPv4 address in

all platforms. [ruby-dev:45671]

* lib/ipaddr.rb: Allow the xxxd.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/trunk@35865 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
This commit is contained in:
knu 2012-06-02 09:18:32 +00:00
parent 70be643c5b
commit 866761d2e8
2 changed files with 149 additions and 86 deletions

View File

@ -1,3 +1,16 @@
Sat Jun 2 18:09:02 2012 Akinori MUSHA <knu@iDaemons.org>
* 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.
Sat Jun 2 16:59:00 2012 NARUSE, Yui <naruse@ruby-lang.org> Sat Jun 2 16:59:00 2012 NARUSE, Yui <naruse@ruby-lang.org>
* string.c (rb_enc_cr_str_buf_cat): don't reset coderange as unknown. * string.c (rb_enc_cr_str_buf_cat): don't reset coderange as unknown.

View File

@ -2,7 +2,7 @@
# ipaddr.rb - A class to manipulate an IP address # ipaddr.rb - A class to manipulate an IP address
# #
# Copyright (c) 2002 Hajimu UMEMOTO <ume@mahoroba.org>. # Copyright (c) 2002 Hajimu UMEMOTO <ume@mahoroba.org>.
# Copyright (c) 2007 Akinori MUSHA <knu@iDaemons.org>. # Copyright (c) 2007, 2009, 2012 Akinori MUSHA <knu@iDaemons.org>.
# All rights reserved. # All rights reserved.
# #
# You can redistribute and/or modify it under the same terms as Ruby. # You can redistribute and/or modify it under the same terms as Ruby.
@ -17,61 +17,6 @@
# #
require 'socket' 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 # IPAddr provides a set of methods to manipulate an IP address. Both IPv4 and
# IPv6 are supported. # IPv6 are supported.
# #
@ -102,6 +47,41 @@ class IPAddr
# Format string for IPv6 # Format string for IPv6
IN6FORMAT = (["%.4x"] * 8).join(':') 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. # Returns the address family of this IP address.
attr_reader :family attr_reader :family
@ -212,7 +192,7 @@ class IPAddr
str.gsub!(/\b0{1,3}([\da-f]+)\b/i, '\1') str.gsub!(/\b0{1,3}([\da-f]+)\b/i, '\1')
loop do 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:0\b/, ':')
break if str.sub!(/\b0: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/, ':') break if str.sub!(/\b0:0:0:0:0\b/, ':')
@ -223,7 +203,7 @@ class IPAddr
end end
str.sub!(/:{3,}/, '::') 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) str = sprintf('::%s%d.%d.%d.%d', $1, $2.hex / 256, $2.hex % 256, $3.hex / 256, $3.hex % 256)
end end
@ -490,11 +470,6 @@ class IPAddr
# It seems AI_NUMERICHOST doesn't do the job. # It seems AI_NUMERICHOST doesn't do the job.
#Socket.getaddrinfo(left, nil, Socket::AF_INET6, Socket::SOCK_STREAM, nil, #Socket.getaddrinfo(left, nil, Socket::AF_INET6, Socket::SOCK_STREAM, nil,
# Socket::AI_NUMERICHOST) # Socket::AI_NUMERICHOST)
begin
IPSocket.getaddress(prefix) # test if address is valid
rescue
raise ArgumentError, "invalid address"
end
@addr = @family = nil @addr = @family = nil
if family == Socket::AF_UNSPEC || family == Socket::AF_INET if family == Socket::AF_UNSPEC || family == Socket::AF_INET
@addr = in_addr(prefix) @addr = in_addr(prefix)
@ -528,26 +503,44 @@ class IPAddr
end end
def in_addr(addr) def in_addr(addr)
if addr =~ /^\d+\.\d+\.\d+\.\d+$/ case addr
return addr.split('.').inject(0) { |i, s| when Array
i << 8 | s.to_i octets = addr
} else
m = RE_IPV4ADDRLIKE.match(addr) or return nil
octets = m.captures
end 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 end
def in6_addr(left) def in6_addr(left)
case left case left
when /^::ffff:(\d+\.\d+\.\d+\.\d+)$/i when RE_IPV6ADDRLIKE_FULL
return in_addr($1) + 0xffff00000000 if $2
when /^::(\d+\.\d+\.\d+\.\d+)$/i addr = in_addr($~[2,4])
return in_addr($1) left = $1 + ':'
when /[^0-9a-f:]/i
raise ArgumentError, "invalid address"
when /^(.*)::(.*)$/
left, right = $1, $2
else else
addr = 0
end
right = '' 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 end
l = left.split(':') l = left.split(':')
r = right.split(':') r = right.split(':')
@ -555,9 +548,9 @@ class IPAddr
if rest < 0 if rest < 0
return nil return nil
end 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 i << 16 | s.hex
} } | addr
end end
def addr_mask(addr) def addr_mask(addr)
@ -599,6 +592,55 @@ class IPAddr
end 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__ if $0 == __FILE__
eval DATA.read, nil, $0, __LINE__+4 eval DATA.read, nil, $0, __LINE__+4
end end
@ -609,10 +651,15 @@ require 'test/unit'
class TC_IPAddr < Test::Unit::TestCase class TC_IPAddr < Test::Unit::TestCase
def test_s_new def test_s_new
[
["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 { assert_nothing_raised {
IPAddr.new("3FFE:505:ffff::/48") IPAddr.new(*args)
IPAddr.new("0:0:0:1::") }
IPAddr.new("2001:200:300::/48")
} }
a = IPAddr.new 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) assert_equal("2001:200:300::", IPAddr.new("[2001:200:300::]/48").to_s)
[ [
["192.168.0.256"],
["192.168.0.011"],
["fe80::1%fxp0"], ["fe80::1%fxp0"],
["::1/255.255.255.0"], ["::1/255.255.255.0"],
["::1:192.168.1.2/120"],
[IPAddr.new("::1").to_i], [IPAddr.new("::1").to_i],
["::ffff:192.168.1.2/120", Socket::AF_INET], ["::ffff:192.168.1.2/120", Socket::AF_INET],
["[192.168.1.2]/120"], ["[192.168.1.2]/120"],
@ -811,7 +859,9 @@ class TC_Operator < Test::Unit::TestCase
end end
def test_equal 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(false, @a == IPAddr.new("3ffe:505:3::"))
assert_equal(true, @a != IPAddr.new("3ffe:505:3::")) assert_equal(true, @a != IPAddr.new("3ffe:505:3::"))
assert_equal(false, @a != IPAddr.new("3ffe:505:2::")) assert_equal(false, @a != IPAddr.new("3ffe:505:2::"))