Skip to content

Commit

Permalink
Add parsing of shorthand IPv4 addresses (compatible with inet_aton)
Browse files Browse the repository at this point in the history
Many applications (like browsers, curl, and wget) and even
Ruby's own Net::HTTP library accepts shorthand IPv4 addresses
like 127.1 or 2130706433 for 127.0.0.1.

It is confusing that IPAddr can't accept them.
  • Loading branch information
Envek committed Mar 29, 2019
1 parent 2426b56 commit 70ef24a
Show file tree
Hide file tree
Showing 3 changed files with 39 additions and 10 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
-----

Expand Down
29 changes: 20 additions & 9 deletions lib/ipaddr.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand Down Expand Up @@ -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)
Expand Down
18 changes: 17 additions & 1 deletion test/test_ipaddr.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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") }
Expand All @@ -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|
Expand Down

0 comments on commit 70ef24a

Please sign in to comment.