diff --git a/gson/src/main/java/com/google/gson/internal/bind/TypeAdapters.java b/gson/src/main/java/com/google/gson/internal/bind/TypeAdapters.java index 45f9536b54..06b0eb9727 100644 --- a/gson/src/main/java/com/google/gson/internal/bind/TypeAdapters.java +++ b/gson/src/main/java/com/google/gson/internal/bind/TypeAdapters.java @@ -761,11 +761,17 @@ public void write(JsonWriter out, URI value) throws IOException { public static final TypeAdapter INET_ADDRESS = new TypeAdapter() { - // A pattern that matches every IP address and no DNS address. It matches plenty of things - // that aren't either of those, which is fine. An IPv4 address is n.n.n.n where each n is a - // non-negative integer. An IPv6 address contains at least one colon. (There are further - // constraints in both cases, but they don't matter here.) - private final Pattern ipAddressPattern = Pattern.compile(".*:.*|[0-9]+(\\.[0-9]+){3}"); + // A pattern that matches every IP address and no DNS address. It matches some things that + // aren't either of those, but only ones that InetAddress.getByName will reject without + // attempting a lookup. An IPv4 address is n.n.n.n where each n is between 0 and 255, + // written with at most three digits (getByName accepts leading zeros such as "001"). + // An IPv6 address contains at least one colon, and getByName only tries to parse a string + // as an IP literal (rather than looking it up) if it starts with a hex digit, a colon, or + // an opening bracket; a string that it tries to parse and that contains a colon is either + // parsed or rejected. + private static final String IPV4_PART = "(25[0-5]|2[0-4][0-9]|[01]?[0-9]?[0-9])"; + private final Pattern ipAddressPattern = + Pattern.compile("[0-9A-Fa-f:\\[].*:.*|" + IPV4_PART + "(\\." + IPV4_PART + "){3}"); @Override public InetAddress read(JsonReader in) throws IOException { diff --git a/gson/src/test/java/com/google/gson/DefaultInetAddressTypeAdapterTest.java b/gson/src/test/java/com/google/gson/DefaultInetAddressTypeAdapterTest.java index d4a0d1aae7..8f807015ae 100644 --- a/gson/src/test/java/com/google/gson/DefaultInetAddressTypeAdapterTest.java +++ b/gson/src/test/java/com/google/gson/DefaultInetAddressTypeAdapterTest.java @@ -67,6 +67,40 @@ public void testInetAddressDeserializeNonIpAddress() { .startsWith("Failed parsing 'localhost' as InetAddress; at path $"); } + @Test + public void testInetAddressDeserializeIpLikeNonIpAddress() { + // These look like IP addresses but are not IP literals that InetAddress.getByName parses. + for (String address : + new String[] {"256.1.1.1", "300.1.1.1", "1.2.3.999", "0001.2.3.4", "zz:1", "g::1"}) { + String jsonAddress = "\"" + address + "\""; + JsonSyntaxException e = + assertThrows( + JsonSyntaxException.class, () -> gson.fromJson(jsonAddress, InetAddress.class)); + assertThat(e) + .hasMessageThat() + .startsWith("Failed parsing '" + address + "' as InetAddress; at path $"); + } + } + + @Test + public void testInetAddressDeserializeIpAddressForms() throws Exception { + for (String address : + new String[] { + "0.0.0.0", + "255.255.255.255", + "01.2.3.4", + "001.002.003.004", + "::1", + "[::1]", + "::ffff:1.2.3.4", + "fe80::1%1" + }) { + String jsonAddress = "\"" + address + "\""; + assertThat(gson.fromJson(jsonAddress, InetAddress.class)) + .isEqualTo(InetAddress.getByName(address)); + } + } + @Test public void testInetAddressDeserializeNonIpAddressAllowed() throws Exception { String jsonAddress = "\"localhost\"";