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.FileInputStream;
27  import java.io.IOException;
28  import java.io.InputStreamReader;
29  import java.io.Reader;
30  import java.net.Inet4Address;
31  import java.net.Inet6Address;
32  import java.net.InetAddress;
33  import java.nio.charset.Charset;
34  import java.util.ArrayList;
35  import java.util.List;
36  import java.util.Locale;
37  import java.util.HashMap;
38  import java.util.Map;
39  import java.util.regex.Pattern;
40  
41  import static io.netty.util.internal.ObjectUtil.*;
42  
43  /**
44   * A parser for hosts files.
45   */
46  @UnstableApi
47  public final class HostsFileParser {
48  
49      private static final String WINDOWS_DEFAULT_SYSTEM_ROOT = "C:\\Windows";
50      private static final String WINDOWS_HOSTS_FILE_RELATIVE_PATH = "\\system32\\drivers\\etc\\hosts";
51      private static final String X_PLATFORMS_HOSTS_FILE_PATH = "/etc/hosts";
52  
53      private static final Pattern WHITESPACES = Pattern.compile("[ \t]+");
54  
55      private static final InternalLogger logger = InternalLoggerFactory.getInstance(HostsFileParser.class);
56  
57      private static File locateHostsFile() {
58          File hostsFile;
59          if (PlatformDependent.isWindows()) {
60              hostsFile = new File(System.getenv("SystemRoot") + WINDOWS_HOSTS_FILE_RELATIVE_PATH);
61              if (!hostsFile.exists()) {
62                  hostsFile = new File(WINDOWS_DEFAULT_SYSTEM_ROOT + WINDOWS_HOSTS_FILE_RELATIVE_PATH);
63              }
64          } else {
65              hostsFile = new File(X_PLATFORMS_HOSTS_FILE_PATH);
66          }
67          return hostsFile;
68      }
69  
70      /**
71       * Parse hosts file at standard OS location using the systems default {@link Charset} for decoding.
72       *
73       * @return a {@link HostsFileEntries}
74       */
75      public static HostsFileEntries parseSilently() {
76          return parseSilently(Charset.defaultCharset());
77      }
78  
79      /**
80       * Parse hosts file at standard OS location using the given {@link Charset}s one after each other until
81       * we were able to parse something or none is left.
82       *
83       * @param charsets the {@link Charset}s to try as file encodings when parsing.
84       * @return a {@link HostsFileEntries}
85       */
86      public static HostsFileEntries parseSilently(Charset... charsets) {
87          File hostsFile = locateHostsFile();
88          try {
89              return parse(hostsFile, charsets);
90          } catch (IOException e) {
91              if (logger.isWarnEnabled()) {
92                  logger.warn("Failed to load and parse hosts file at " + hostsFile.getPath(), e);
93              }
94              return HostsFileEntries.EMPTY;
95          }
96      }
97  
98      /**
99       * Parse hosts file at standard OS location using the system default {@link Charset} for decoding.
100      *
101      * @return a {@link HostsFileEntries}
102      * @throws IOException file could not be read
103      */
104     public static HostsFileEntries parse() throws IOException {
105         return parse(locateHostsFile());
106     }
107 
108     /**
109      * Parse a hosts file using the system default {@link Charset} for decoding.
110      *
111      * @param file the file to be parsed
112      * @return a {@link HostsFileEntries}
113      * @throws IOException file could not be read
114      */
115     public static HostsFileEntries parse(File file) throws IOException {
116         return parse(file, Charset.defaultCharset());
117     }
118 
119     /**
120      * Parse a hosts file.
121      *
122      * @param file the file to be parsed
123      * @param charsets the {@link Charset}s to try as file encodings when parsing.
124      * @return a {@link HostsFileEntries}
125      * @throws IOException file could not be read
126      */
127     public static HostsFileEntries parse(File file, Charset... charsets) throws IOException {
128         checkNotNull(file, "file");
129         checkNotNull(charsets, "charsets");
130         if (file.exists() && file.isFile()) {
131             for (Charset charset: charsets) {
132                 HostsFileEntries entries = parse(new BufferedReader(new InputStreamReader(
133                         new FileInputStream(file), charset)));
134                 if (entries != HostsFileEntries.EMPTY) {
135                     return entries;
136                 }
137             }
138         }
139         return HostsFileEntries.EMPTY;
140     }
141 
142     /**
143      * Parse a reader of hosts file format.
144      *
145      * @param reader the file to be parsed
146      * @return a {@link HostsFileEntries}
147      * @throws IOException file could not be read
148      */
149     public static HostsFileEntries parse(Reader reader) throws IOException {
150         checkNotNull(reader, "reader");
151         BufferedReader buff = new BufferedReader(reader);
152         try {
153             Map<String, Inet4Address> ipv4Entries = new HashMap<String, Inet4Address>();
154             Map<String, Inet6Address> ipv6Entries = new HashMap<String, Inet6Address>();
155             String line;
156             while ((line = buff.readLine()) != null) {
157                 // remove comment
158                 int commentPosition = line.indexOf('#');
159                 if (commentPosition != -1) {
160                     line = line.substring(0, commentPosition);
161                 }
162                 // skip empty lines
163                 line = line.trim();
164                 if (line.isEmpty()) {
165                     continue;
166                 }
167 
168                 // split
169                 List<String> lineParts = new ArrayList<String>();
170                 for (String s: WHITESPACES.split(line)) {
171                     if (!s.isEmpty()) {
172                         lineParts.add(s);
173                     }
174                 }
175 
176                 // a valid line should be [IP, hostname, alias*]
177                 if (lineParts.size() < 2) {
178                     // skip invalid line
179                     continue;
180                 }
181 
182                 byte[] ipBytes = NetUtil.createByteArrayFromIpAddressString(lineParts.get(0));
183 
184                 if (ipBytes == null) {
185                     // skip invalid IP
186                     continue;
187                 }
188 
189                 // loop over hostname and aliases
190                 for (int i = 1; i < lineParts.size(); i ++) {
191                     String hostname = lineParts.get(i);
192                     String hostnameLower = hostname.toLowerCase(Locale.ENGLISH);
193                     InetAddress address = InetAddress.getByAddress(hostname, ipBytes);
194                     if (address instanceof Inet4Address) {
195                         Inet4Address previous = ipv4Entries.put(hostnameLower, (Inet4Address) address);
196                         if (previous != null) {
197                             // restore, we want to keep the first entry
198                             ipv4Entries.put(hostnameLower, previous);
199                         }
200                     } else {
201                         Inet6Address previous = ipv6Entries.put(hostnameLower, (Inet6Address) address);
202                         if (previous != null) {
203                             // restore, we want to keep the first entry
204                             ipv6Entries.put(hostnameLower, previous);
205                         }
206                     }
207                 }
208             }
209             return ipv4Entries.isEmpty() && ipv6Entries.isEmpty() ?
210                     HostsFileEntries.EMPTY :
211                     new HostsFileEntries(ipv4Entries, ipv6Entries);
212         } finally {
213             try {
214                 buff.close();
215             } catch (IOException e) {
216                 logger.warn("Failed to close a reader", e);
217             }
218         }
219     }
220 
221     /**
222      * Can't be instantiated.
223      */
224     private HostsFileParser() {
225     }
226 }