Repository: netty/netty
Issue: HostsFileParser tries to resolve hostnames as case-sensitive
Labels: defect

Netty version: 4.1.0.CR7

Context:
I am implementing a mail client in vert.x, which uses netty as network library, when trying to connect to LOCALHOST in upper case, the dns client gives an UnknownHostException.
The regular behaviour of the /etc/hosts resolution should be match the hostnames case insensitive

Steps to reproduce:
run the following unit test:
https://gist.github.com/alexlehm/e921fca3c368617be29cc7432b376927

the test using "localhost" resolves, while the test using "LOCALHOST" fails

java -version
java version "1.8.0_45"
Java(TM) SE Runtime Environment (build 1.8.0_45-b14)
Java HotSpot(TM) 64-Bit Server VM (build 25.45-b02, mixed mode)

Operating system: Windows 7 64-bit

I would guess the error is that the entries Map has to use hostname as case folded key here https://github.com/netty/netty/blob/4.1/resolver/src/main/java/io/netty/resolver/HostsFileParser.java#L157 and here https://github.com/netty/netty/blob/4.1/resolver/src/main/java/io/netty/resolver/DefaultHostsFileEntriesResolver.java#L30


Issue comments:
Should be pretty easy to contribute a fix :)

Included the unit test in the pr and added the fix for the issue
https://github.com/netty/netty/pull/5189
I will apply for clearance to participate in the project to be able to sign the CLA

Fixed by #5189


Pull request review comments:

Patch that resolved the issue:
diff --git a/resolver/src/main/java/io/netty/resolver/DefaultHostsFileEntriesResolver.java b/resolver/src/main/java/io/netty/resolver/DefaultHostsFileEntriesResolver.java
index 3d19042e5cc..dbf5fb9ca8e 100644
--- a/resolver/src/main/java/io/netty/resolver/DefaultHostsFileEntriesResolver.java
+++ b/resolver/src/main/java/io/netty/resolver/DefaultHostsFileEntriesResolver.java
@@ -16,6 +16,7 @@
 package io.netty.resolver;
 
 import java.net.InetAddress;
+import java.util.Locale;
 import java.util.Map;
 
 /**
@@ -27,6 +28,6 @@ public final class DefaultHostsFileEntriesResolver implements HostsFileEntriesRe
 
     @Override
     public InetAddress address(String inetHost) {
-        return entries.get(inetHost);
+        return entries.get(inetHost.toLowerCase(Locale.ENGLISH));
     }
 }
diff --git a/resolver/src/main/java/io/netty/resolver/HostsFileParser.java b/resolver/src/main/java/io/netty/resolver/HostsFileParser.java
index 0e8b9163c71..a17a0f7068a 100644
--- a/resolver/src/main/java/io/netty/resolver/HostsFileParser.java
+++ b/resolver/src/main/java/io/netty/resolver/HostsFileParser.java
@@ -29,6 +29,7 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.Locale;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.regex.Pattern;
@@ -151,10 +152,11 @@ public static Map<String, InetAddress> parse(Reader reader) throws IOException {
                 // loop over hostname and aliases
                 for (int i = 1; i < lineParts.size(); i ++) {
                     String hostname = lineParts.get(i);
-                    if (!entries.containsKey(hostname)) {
+                    String hostnameLower = hostname.toLowerCase(Locale.ENGLISH);
+                    if (!entries.containsKey(hostnameLower)) {
                         // trying to map a host to multiple IPs is wrong
                         // only the first entry is honored
-                        entries.put(hostname, InetAddress.getByAddress(hostname, ipBytes));
+                        entries.put(hostnameLower, InetAddress.getByAddress(hostname, ipBytes));
                     }
                 }
             }


Test patch:
diff --git a/resolver/src/test/java/io/netty/resolver/DefaultHostsFileEntriesResolverTest.java b/resolver/src/test/java/io/netty/resolver/DefaultHostsFileEntriesResolverTest.java
new file mode 100644
index 00000000000..7fac11b2d5a
--- /dev/null
+++ b/resolver/src/test/java/io/netty/resolver/DefaultHostsFileEntriesResolverTest.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2016 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+/**
+ * show issue https://github.com/netty/netty/issues/5182
+ * HostsFileParser tries to resolve hostnames as case-sensitive
+ */
+package io.netty.resolver;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertNotNull;
+
+public class DefaultHostsFileEntriesResolverTest {
+
+    @Test
+    public void testLocalhost() {
+        DefaultHostsFileEntriesResolver resolver = new DefaultHostsFileEntriesResolver();
+        assertNotNull("localhost doesn't resolve", resolver.address("localhost"));
+        assertNotNull("LOCALHOST doesn't resolve", resolver.address("LOCALHOST"));
+    }
+}
diff --git a/resolver/src/test/java/io/netty/resolver/HostsFileParserTest.java b/resolver/src/test/java/io/netty/resolver/HostsFileParserTest.java
index d3261338957..bc059107b90 100644
--- a/resolver/src/test/java/io/netty/resolver/HostsFileParserTest.java
+++ b/resolver/src/test/java/io/netty/resolver/HostsFileParserTest.java
@@ -38,16 +38,20 @@ public void testParse() throws IOException {
                 .append("192.168.0.2  host3  #comment").append("\n") // comment after hostname
                 .append("192.168.0.3  host4  host5 host6").append("\n") // multiple aliases
                 .append("192.168.0.4  host4").append("\n") // host mapped to a second address, must be ignored
+                .append("192.168.0.5  HOST7").append("\n") // uppercase host, should match lowercase host
+                .append("192.168.0.6  host7").append("\n") // should be ignored since we have the uppercase host already
                 .toString();
 
         Map<String, InetAddress> entries = HostsFileParser.parse(new BufferedReader(new StringReader(hostsString)));
 
-        assertEquals("Expected 6 entries", 6, entries.size());
+        assertEquals("Expected 7 entries", 7, entries.size());
         assertEquals("127.0.0.1", entries.get("host1").getHostAddress());
         assertEquals("192.168.0.1", entries.get("host2").getHostAddress());
         assertEquals("192.168.0.2", entries.get("host3").getHostAddress());
         assertEquals("192.168.0.3", entries.get("host4").getHostAddress());
         assertEquals("192.168.0.3", entries.get("host5").getHostAddress());
         assertEquals("192.168.0.3", entries.get("host6").getHostAddress());
+        assertNotNull("uppercase host doesn't resolve", entries.get("host7"));
+        assertEquals("192.168.0.5", entries.get("host7").getHostAddress());
     }
 }