diff --git a/CHANGELOG.md b/CHANGELOG.md index 0826fa0..327cbe6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,5 @@ +- Change IPv4 address parsing to recognize shorthand addresses, like [inet_aton](https://linux.die.net/man/3/inet_aton) does. [Feature #15734](https://bugs.ruby-lang.org/issues/15734) + 1.2.2 ----- diff --git a/lib/ipaddr.rb b/lib/ipaddr.rb index 7fff54b..df72404 100644 --- a/lib/ipaddr.rb +++ b/lib/ipaddr.rb @@ -49,11 +49,17 @@ class IPAddr IN6FORMAT = (["%.4x"] * 8).join(':') # Regexp _internally_ used for parsing IPv4 address. + # Accepts shorthand forms like +inet_aton+ does: + # 127.0.0.1 = 127.1 = 0x7f.1 = 0177.1 = + # 192.168.255.255 = 192.168.65535 = 0xC0.0xA8FFFF = 3232301055 RE_IPV4ADDRLIKE = %r{ - \A - (\d+) \. (\d+) \. (\d+) \. (\d+) - \z - }x + \A + (?:((?:0x?)?[\da-f]{1,3})\.)? + (?:((?:0x?)?[\da-f]{1,3})\.)? + (?:((?:0x?)?[\da-f]{1,3})\.)? + (?:((?:0x?)?[\da-f]{1,10})) + \z + }xi # Regexp _internally_ used for parsing IPv6 address. RE_IPV6ADDRLIKE_FULL = %r{ @@ -617,11 +623,16 @@ def in_addr(addr) m = RE_IPV4ADDRLIKE.match(addr) or return nil octets = m.captures end - octets.inject(0) { |i, s| - (n = s.to_i) < 256 or raise InvalidAddressError, "invalid address" - s.match(/\A0./) and raise InvalidAddressError, "zero-filled number in IPv4 address is ambiguous" - i << 8 | n - } + octets.each.with_index.inject(0) do |address, (octet, index)| + address = address << 8 + next address unless octet + + n = Integer(octet) + max_exponent = index == 3 ? 8 * (1 + octets.count(&:nil?)) : 8 + n < 2**max_exponent or raise InvalidAddressError, "invalid address" + + address | n + end end def in6_addr(left) diff --git a/test/test_ipaddr.rb b/test/test_ipaddr.rb index 1aa24eb..bcd100e 100644 --- a/test/test_ipaddr.rb +++ b/test/test_ipaddr.rb @@ -86,7 +86,6 @@ def test_s_new assert_equal("0:2:3:4:5:6:7:8", IPAddr.new("::2:3:4:5:6:7:8").to_s) assert_raise(IPAddr::InvalidAddressError) { IPAddr.new("192.168.0.256") } - assert_raise(IPAddr::InvalidAddressError) { IPAddr.new("192.168.0.011") } assert_raise(IPAddr::InvalidAddressError) { IPAddr.new("fe80::1%fxp0") } assert_raise(IPAddr::InvalidAddressError) { IPAddr.new("[192.168.1.2]/120") } assert_raise(IPAddr::InvalidAddressError) { IPAddr.new("[2001:200:300::]\nINVALID") } @@ -99,6 +98,23 @@ def test_s_new assert_raise(IPAddr::AddressFamilyError) { IPAddr.new("::ffff:192.168.1.2/120", Socket::AF_INET) } end + def test_s_new_aton_compatible + assert_equal("127.0.0.1", IPAddr.new("127.1").to_s) + assert_equal("127.0.0.2", IPAddr.new("0x7F.2").to_s) + assert_equal("127.0.0.42", IPAddr.new("0177.42").to_s) + assert_equal("127.0.0.34", IPAddr.new("0x7f.042").to_s) + assert_equal("127.0.0.1", IPAddr.new("2130706433").to_s) + assert_equal("255.255.255.255", IPAddr.new("4294967295").to_s) + assert_equal("192.168.1.1", IPAddr.new("192.168.257").to_s) + assert_equal("10.1.136.148", IPAddr.new("10.100500").to_s) + assert_equal("192.168.0.9", IPAddr.new("192.168.0.011").to_s) + assert_equal("192.168.255.255", IPAddr.new("0xC0.0xA8FFFF").to_s) + + assert_raise(IPAddr::InvalidAddressError) { IPAddr.new("192.168.0.256") } + assert_raise(IPAddr::InvalidAddressError) { IPAddr.new("256.168.0.1") } + assert_raise(IPAddr::InvalidAddressError) { IPAddr.new("4294967296") } + end + def test_s_new_ntoh addr = '' IPAddr.new("1234:5678:9abc:def0:1234:5678:9abc:def0").hton.each_byte { |c|