diff --git a/CHANGELOG.md b/CHANGELOG.md index 41804a5..f7d2cb8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,5 @@ +- Recognize shorthand IPv4 addresses like [inet_aton](https://linux.die.net/man/3/inet_aton) does. [\#12](https://github.com/ruby/ipaddr/pull/12) ([Envek](https://github.com/Envek)) + 1.3.0 ----- diff --git a/lib/ipaddr.rb b/lib/ipaddr.rb index 59427bc..605db7a 100644 --- a/lib/ipaddr.rb +++ b/lib/ipaddr.rb @@ -51,11 +51,17 @@ class IPAddr IN6FORMAT = (["%.4x"] * 8).join(':').freeze # Regexp _internally_ used for parsing IPv4 address. + # Accepts shorthand forms like +inet_aton+ does: + # 127.0.0.1 = 127.1 = 0x7f.1 = 0177.1 = 2130706433 + # 192.168.255.255 = 192.168.65535 = 0xC0.0xA8FFFF = 3232301055 RE_IPV4ADDRLIKE = %r{ \A - \d+ \. \d+ \. \d+ \. \d+ + (?:((?:0x?)?[\da-f]{1,3})\.)? + (?:((?:0x?)?[\da-f]{1,3})\.)? + (?:((?:0x?)?[\da-f]{1,3})\.)? + (?:((?:0x?)?[\da-f]{1,10})) \z - }x + }xi # Regexp _internally_ used for parsing IPv6 address. RE_IPV6ADDRLIKE_FULL = %r{ @@ -787,14 +793,19 @@ def in_addr(addr) when Array octets = addr else - RE_IPV4ADDRLIKE.match?(addr) or return nil - octets = addr.split('.') - end - octets.inject(0) { |i, s| - (n = s.to_i) < 256 or raise InvalidAddressError, "invalid address: #{addr}" - (s != '0') && s.start_with?('0') and raise InvalidAddressError, "zero-filled number in IPv4 address is ambiguous: #{addr}" - i << 8 | n - } + m = RE_IPV4ADDRLIKE.match(addr) or return nil + octets = m.captures + end + 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: #{addr}" + + address | n + end end def in6_addr(left) diff --git a/test/test_ipaddr.rb b/test/test_ipaddr.rb index 6af7b73..61a8ec3 100644 --- a/test/test_ipaddr.rb +++ b/test/test_ipaddr.rb @@ -107,17 +107,16 @@ 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%") } assert_raise(IPAddr::InvalidAddressError) { IPAddr.new("fe80::1%]") } assert_raise(IPAddr::InvalidAddressError) { IPAddr.new("[192.168.1.2]") } assert_raise(IPAddr::InvalidAddressError) { IPAddr.new("[192.168.1.2]/120") } assert_raise(IPAddr::InvalidAddressError) { IPAddr.new("[2001:200:300::]\nINVALID") } assert_raise(IPAddr::InvalidAddressError) { IPAddr.new("192.168.0.1/32\nINVALID") } - assert_raise(IPAddr::InvalidAddressError) { IPAddr.new("192.168.0.1/32/20") } assert_raise(IPAddr::InvalidAddressError) { IPAddr.new("192.168.1.2", Socket::AF_INET6) } assert_raise(IPAddr::InvalidAddressError) { IPAddr.new("[192.168.1.2]", Socket::AF_INET6) } assert_raise(IPAddr::InvalidAddressError) { IPAddr.new("[192.168.1.2]", Socket::AF_INET) } + assert_raise(IPAddr::InvalidPrefixError) { IPAddr.new("192.168.0.1/32/20") } assert_raise(IPAddr::InvalidPrefixError) { IPAddr.new("192.168.0.1/032") } assert_raise(IPAddr::InvalidPrefixError) { IPAddr.new("::1/0128") } assert_raise(IPAddr::InvalidPrefixError) { IPAddr.new("::1/255.255.255.0") } @@ -131,6 +130,24 @@ def test_s_new assert_raise(IPAddr::AddressFamilyError) { IPAddr.new("[::%eth0]", 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.1.100", IPAddr.new("192.168.01.100").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| @@ -774,11 +791,6 @@ def test_raises_invalid_address_error_with_error_message end assert_equal('invalid address: 192.168.0.1000', e.message) - e = assert_raise(IPAddr::InvalidAddressError) do - IPAddr.new('192.168.01.100') - end - assert_equal('zero-filled number in IPv4 address is ambiguous: 192.168.01.100', e.message) - e = assert_raise(IPAddr::InvalidAddressError) do IPAddr.new('INVALID') end