View Javadoc
1   /*
2    * Copyright 2015 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a copy of the License at:
7    *
8    *   http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
16  package io.netty.resolver;
17  
18  import io.netty.util.NetUtil;
19  import io.netty.util.internal.PlatformDependent;
20  import io.netty.util.internal.UnstableApi;
21  import io.netty.util.internal.logging.InternalLogger;
22  import io.netty.util.internal.logging.InternalLoggerFactory;
23  
24  import java.io.BufferedReader;
25  import java.io.File;
26  import java.io.FileReader;
27  import java.io.IOException;
28  import java.io.Reader;
29  import java.net.Inet4Address;
30  import java.net.Inet6Address;
31  import java.net.InetAddress;
32  import java.util.ArrayList;
33  import java.util.List;
34  import java.util.Locale;
35  import java.util.HashMap;
36  import java.util.Map;
37  import java.util.regex.Pattern;
38  
39  import static io.netty.util.internal.ObjectUtil.*;
40  
41  /**
42   * A parser for hosts files.
43   */
44  @UnstableApi
45  public final class HostsFileParser {
46  
47      private static final String WINDOWS_DEFAULT_SYSTEM_ROOT = "C:\\Windows";
48      private static final String WINDOWS_HOSTS_FILE_RELATIVE_PATH = "\\system32\\drivers\\etc\\hosts";
49      private static final String X_PLATFORMS_HOSTS_FILE_PATH = "/etc/hosts";
50  
51      private static final Pattern WHITESPACES = Pattern.compile("[ \t]+");
52  
53      private static final InternalLogger logger = InternalLoggerFactory.getInstance(HostsFileParser.class);
54  
55      private static File locateHostsFile() {
56          File hostsFile;
57          if (PlatformDependent.isWindows()) {
58              hostsFile = new File(System.getenv("SystemRoot") + WINDOWS_HOSTS_FILE_RELATIVE_PATH);
59              if (!hostsFile.exists()) {
60                  hostsFile = new File(WINDOWS_DEFAULT_SYSTEM_ROOT + WINDOWS_HOSTS_FILE_RELATIVE_PATH);
61              }
62          } else {
63              hostsFile = new File(X_PLATFORMS_HOSTS_FILE_PATH);
64          }
65          return hostsFile;
66      }
67  
68      /**
69       * Parse hosts file at standard OS location.
70       *
71       * @return a {@link HostsFileEntries}
72       */
73      public static HostsFileEntries parseSilently() {
74          File hostsFile = locateHostsFile();
75          try {
76              return parse(hostsFile);
77          } catch (IOException e) {
78              if (logger.isWarnEnabled()) {
79                  logger.warn("Failed to load and parse hosts file at " + hostsFile.getPath(), e);
80              }
81              return HostsFileEntries.EMPTY;
82          }
83      }
84  
85      /**
86       * Parse hosts file at standard OS location.
87       *
88       * @return a {@link HostsFileEntries}
89       * @throws IOException file could not be read
90       */
91      public static HostsFileEntries parse() throws IOException {
92          return parse(locateHostsFile());
93      }
94  
95      /**
96       * Parse a hosts file.
97       *
98       * @param file the file to be parsed
99       * @return a {@link HostsFileEntries}
100      * @throws IOException file could not be read
101      */
102     public static HostsFileEntries parse(File file) throws IOException {
103         checkNotNull(file, "file");
104         if (file.exists() && file.isFile()) {
105             return parse(new BufferedReader(new FileReader(file)));
106         } else {
107             return HostsFileEntries.EMPTY;
108         }
109     }
110 
111     /**
112      * Parse a reader of hosts file format.
113      *
114      * @param reader the file to be parsed
115      * @return a {@link HostsFileEntries}
116      * @throws IOException file could not be read
117      */
118     public static HostsFileEntries parse(Reader reader) throws IOException {
119         checkNotNull(reader, "reader");
120         BufferedReader buff = new BufferedReader(reader);
121         try {
122             Map<String, Inet4Address> ipv4Entries = new HashMap<String, Inet4Address>();
123             Map<String, Inet6Address> ipv6Entries = new HashMap<String, Inet6Address>();
124             String line;
125             while ((line = buff.readLine()) != null) {
126                 // remove comment
127                 int commentPosition = line.indexOf('#');
128                 if (commentPosition != -1) {
129                     line = line.substring(0, commentPosition);
130                 }
131                 // skip empty lines
132                 line = line.trim();
133                 if (line.isEmpty()) {
134                     continue;
135                 }
136 
137                 // split
138                 List<String> lineParts = new ArrayList<String>();
139                 for (String s: WHITESPACES.split(line)) {
140                     if (!s.isEmpty()) {
141                         lineParts.add(s);
142                     }
143                 }
144 
145                 // a valid line should be [IP, hostname, alias*]
146                 if (lineParts.size() < 2) {
147                     // skip invalid line
148                     continue;
149                 }
150 
151                 byte[] ipBytes = NetUtil.createByteArrayFromIpAddressString(lineParts.get(0));
152 
153                 if (ipBytes == null) {
154                     // skip invalid IP
155                     continue;
156                 }
157 
158                 // loop over hostname and aliases
159                 for (int i = 1; i < lineParts.size(); i ++) {
160                     String hostname = lineParts.get(i);
161                     String hostnameLower = hostname.toLowerCase(Locale.ENGLISH);
162                     InetAddress address = InetAddress.getByAddress(hostname, ipBytes);
163                     if (address instanceof Inet4Address) {
164                         Inet4Address previous = ipv4Entries.put(hostnameLower, (Inet4Address) address);
165                         if (previous != null) {
166                             // restore, we want to keep the first entry
167                             ipv4Entries.put(hostnameLower, previous);
168                         }
169                     } else {
170                         Inet6Address previous = ipv6Entries.put(hostnameLower, (Inet6Address) address);
171                         if (previous != null) {
172                             // restore, we want to keep the first entry
173                             ipv6Entries.put(hostnameLower, previous);
174                         }
175                     }
176                 }
177             }
178             return ipv4Entries.isEmpty() && ipv6Entries.isEmpty() ?
179                     HostsFileEntries.EMPTY :
180                     new HostsFileEntries(ipv4Entries, ipv6Entries);
181         } finally {
182             try {
183                 buff.close();
184             } catch (IOException e) {
185                 logger.warn("Failed to close a reader", e);
186             }
187         }
188     }
189 
190     /**
191      * Can't be instantiated.
192      */
193     private HostsFileParser() {
194     }
195 }