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              logger.warn("Failed to load and parse hosts file at " + hostsFile.getPath(), e);
79              return HostsFileEntries.EMPTY;
80          }
81      }
82  
83      /**
84       * Parse hosts file at standard OS location.
85       *
86       * @return a {@link HostsFileEntries}
87       * @throws IOException file could not be read
88       */
89      public static HostsFileEntries parse() throws IOException {
90          return parse(locateHostsFile());
91      }
92  
93      /**
94       * Parse a hosts file.
95       *
96       * @param file the file to be parsed
97       * @return a {@link HostsFileEntries}
98       * @throws IOException file could not be read
99       */
100     public static HostsFileEntries parse(File file) throws IOException {
101         checkNotNull(file, "file");
102         if (file.exists() && file.isFile()) {
103             return parse(new BufferedReader(new FileReader(file)));
104         } else {
105             return HostsFileEntries.EMPTY;
106         }
107     }
108 
109     /**
110      * Parse a reader of hosts file format.
111      *
112      * @param reader the file to be parsed
113      * @return a {@link HostsFileEntries}
114      * @throws IOException file could not be read
115      */
116     public static HostsFileEntries parse(Reader reader) throws IOException {
117         checkNotNull(reader, "reader");
118         BufferedReader buff = new BufferedReader(reader);
119         try {
120             Map<String, Inet4Address> ipv4Entries = new HashMap<String, Inet4Address>();
121             Map<String, Inet6Address> ipv6Entries = new HashMap<String, Inet6Address>();
122             String line;
123             while ((line = buff.readLine()) != null) {
124                 // remove comment
125                 int commentPosition = line.indexOf('#');
126                 if (commentPosition != -1) {
127                     line = line.substring(0, commentPosition);
128                 }
129                 // skip empty lines
130                 line = line.trim();
131                 if (line.isEmpty()) {
132                     continue;
133                 }
134 
135                 // split
136                 List<String> lineParts = new ArrayList<String>();
137                 for (String s: WHITESPACES.split(line)) {
138                     if (!s.isEmpty()) {
139                         lineParts.add(s);
140                     }
141                 }
142 
143                 // a valid line should be [IP, hostname, alias*]
144                 if (lineParts.size() < 2) {
145                     // skip invalid line
146                     continue;
147                 }
148 
149                 byte[] ipBytes = NetUtil.createByteArrayFromIpAddressString(lineParts.get(0));
150 
151                 if (ipBytes == null) {
152                     // skip invalid IP
153                     continue;
154                 }
155 
156                 // loop over hostname and aliases
157                 for (int i = 1; i < lineParts.size(); i ++) {
158                     String hostname = lineParts.get(i);
159                     String hostnameLower = hostname.toLowerCase(Locale.ENGLISH);
160                     InetAddress address = InetAddress.getByAddress(hostname, ipBytes);
161                     if (address instanceof Inet4Address) {
162                         Inet4Address previous = ipv4Entries.put(hostnameLower, (Inet4Address) address);
163                         if (previous != null) {
164                             // restore, we want to keep the first entry
165                             ipv4Entries.put(hostnameLower, previous);
166                         }
167                     } else {
168                         Inet6Address previous = ipv6Entries.put(hostnameLower, (Inet6Address) address);
169                         if (previous != null) {
170                             // restore, we want to keep the first entry
171                             ipv6Entries.put(hostnameLower, previous);
172                         }
173                     }
174                 }
175             }
176             return ipv4Entries.isEmpty() && ipv6Entries.isEmpty() ?
177                     HostsFileEntries.EMPTY :
178                     new HostsFileEntries(ipv4Entries, ipv6Entries);
179         } finally {
180             try {
181                 buff.close();
182             } catch (IOException e) {
183                 logger.warn("Failed to close a reader", e);
184             }
185         }
186     }
187 
188     /**
189      * Can't be instantiated.
190      */
191     private HostsFileParser() {
192     }
193 }