1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package io.netty5.resolver;
17
18 import io.netty5.util.NetUtil;
19 import io.netty5.util.internal.PlatformDependent;
20 import io.netty5.util.internal.logging.InternalLogger;
21 import io.netty5.util.internal.logging.InternalLoggerFactory;
22
23 import java.io.BufferedReader;
24 import java.io.File;
25 import java.io.FileInputStream;
26 import java.io.IOException;
27 import java.io.InputStreamReader;
28 import java.io.Reader;
29 import java.net.Inet4Address;
30 import java.net.InetAddress;
31 import java.nio.charset.Charset;
32 import java.util.ArrayList;
33 import java.util.Collections;
34 import java.util.HashMap;
35 import java.util.List;
36 import java.util.Locale;
37 import java.util.Map;
38 import java.util.regex.Pattern;
39
40 import static java.util.Objects.requireNonNull;
41
42
43
44
45 public final class HostsFileEntriesProvider {
46
47 public interface Parser {
48
49
50
51
52
53
54
55 HostsFileEntriesProvider parse() throws IOException;
56
57
58
59
60
61
62
63
64
65 HostsFileEntriesProvider parse(Charset... charsets) throws IOException;
66
67
68
69
70
71
72
73
74
75
76
77
78 HostsFileEntriesProvider parse(File file, Charset... charsets) throws IOException;
79
80
81
82
83
84
85
86 HostsFileEntriesProvider parse(Reader reader) throws IOException;
87
88
89
90
91
92
93 HostsFileEntriesProvider parseSilently();
94
95
96
97
98
99
100
101
102 HostsFileEntriesProvider parseSilently(Charset... charsets);
103
104
105
106
107
108
109
110
111
112
113
114 HostsFileEntriesProvider parseSilently(File file, Charset... charsets);
115 }
116
117
118
119
120
121
122 public static Parser parser() {
123 return ParserImpl.INSTANCE;
124 }
125
126 static final HostsFileEntriesProvider EMPTY =
127 new HostsFileEntriesProvider(
128 Collections.<String, List<InetAddress>>emptyMap(),
129 Collections.<String, List<InetAddress>>emptyMap());
130
131 private final Map<String, List<InetAddress>> ipv4Entries;
132 private final Map<String, List<InetAddress>> ipv6Entries;
133
134 HostsFileEntriesProvider(Map<String, List<InetAddress>> ipv4Entries, Map<String, List<InetAddress>> ipv6Entries) {
135 this.ipv4Entries = Collections.unmodifiableMap(new HashMap<>(ipv4Entries));
136 this.ipv6Entries = Collections.unmodifiableMap(new HashMap<>(ipv6Entries));
137 }
138
139
140
141
142
143
144 public Map<String, List<InetAddress>> ipv4Entries() {
145 return ipv4Entries;
146 }
147
148
149
150
151
152
153 public Map<String, List<InetAddress>> ipv6Entries() {
154 return ipv6Entries;
155 }
156
157 private static final class ParserImpl implements Parser {
158
159 private static final String WINDOWS_DEFAULT_SYSTEM_ROOT = "C:\\Windows";
160 private static final String WINDOWS_HOSTS_FILE_RELATIVE_PATH = "\\system32\\drivers\\etc\\hosts";
161 private static final String X_PLATFORMS_HOSTS_FILE_PATH = "/etc/hosts";
162
163 private static final Pattern WHITESPACES = Pattern.compile("[ \t]+");
164
165 private static final InternalLogger logger = InternalLoggerFactory.getInstance(Parser.class);
166
167 static final ParserImpl INSTANCE = new ParserImpl();
168
169 private ParserImpl() {
170
171 }
172
173 @Override
174 public HostsFileEntriesProvider parse() throws IOException {
175 return parse(locateHostsFile(), Charset.defaultCharset());
176 }
177
178 @Override
179 public HostsFileEntriesProvider parse(Charset... charsets) throws IOException {
180 return parse(locateHostsFile(), charsets);
181 }
182
183 @Override
184 public HostsFileEntriesProvider parse(File file, Charset... charsets) throws IOException {
185 requireNonNull(file, "file");
186 requireNonNull(charsets, "charsets");
187 if (charsets.length == 0) {
188 charsets = new Charset[]{Charset.defaultCharset()};
189 }
190 if (file.exists() && file.isFile()) {
191 for (Charset charset : charsets) {
192 try (BufferedReader reader = new BufferedReader(
193 new InputStreamReader(new FileInputStream(file), charset))) {
194 HostsFileEntriesProvider entries = parse(reader);
195 if (entries != HostsFileEntriesProvider.EMPTY) {
196 return entries;
197 }
198 }
199 }
200 }
201 return HostsFileEntriesProvider.EMPTY;
202 }
203
204 @Override
205 public HostsFileEntriesProvider parse(Reader reader) throws IOException {
206 requireNonNull(reader, "reader");
207 BufferedReader buff = new BufferedReader(reader);
208 try {
209 Map<String, List<InetAddress>> ipv4Entries = new HashMap<>();
210 Map<String, List<InetAddress>> ipv6Entries = new HashMap<>();
211 String line;
212 while ((line = buff.readLine()) != null) {
213
214 int commentPosition = line.indexOf('#');
215 if (commentPosition != -1) {
216 line = line.substring(0, commentPosition);
217 }
218
219 line = line.trim();
220 if (line.isEmpty()) {
221 continue;
222 }
223
224
225 List<String> lineParts = new ArrayList<>();
226 for (String s : WHITESPACES.split(line)) {
227 if (!s.isEmpty()) {
228 lineParts.add(s);
229 }
230 }
231
232
233 if (lineParts.size() < 2) {
234
235 continue;
236 }
237
238 byte[] ipBytes = NetUtil.createByteArrayFromIpAddressString(lineParts.get(0));
239
240 if (ipBytes == null) {
241
242 continue;
243 }
244
245
246 for (int i = 1; i < lineParts.size(); i++) {
247 String hostname = lineParts.get(i);
248 String hostnameLower = hostname.toLowerCase(Locale.ENGLISH);
249 InetAddress address = InetAddress.getByAddress(hostname, ipBytes);
250 List<InetAddress> addresses;
251 if (address instanceof Inet4Address) {
252 addresses = ipv4Entries.get(hostnameLower);
253 if (addresses == null) {
254 addresses = new ArrayList<>();
255 ipv4Entries.put(hostnameLower, addresses);
256 }
257 } else {
258 addresses = ipv6Entries.get(hostnameLower);
259 if (addresses == null) {
260 addresses = new ArrayList<>();
261 ipv6Entries.put(hostnameLower, addresses);
262 }
263 }
264 addresses.add(address);
265 }
266 }
267 return ipv4Entries.isEmpty() && ipv6Entries.isEmpty() ?
268 HostsFileEntriesProvider.EMPTY :
269 new HostsFileEntriesProvider(ipv4Entries, ipv6Entries);
270 } finally {
271 try {
272 buff.close();
273 } catch (IOException e) {
274 logger.warn("Failed to close a reader", e);
275 }
276 }
277 }
278
279 @Override
280 public HostsFileEntriesProvider parseSilently() {
281 return parseSilently(locateHostsFile(), Charset.defaultCharset());
282 }
283
284 @Override
285 public HostsFileEntriesProvider parseSilently(Charset... charsets) {
286 return parseSilently(locateHostsFile(), charsets);
287 }
288
289 @Override
290 public HostsFileEntriesProvider parseSilently(File file, Charset... charsets) {
291 try {
292 return parse(file, charsets);
293 } catch (IOException e) {
294 if (logger.isWarnEnabled()) {
295 logger.warn("Failed to load and parse hosts file at " + file.getPath(), e);
296 }
297 return HostsFileEntriesProvider.EMPTY;
298 }
299 }
300
301 private static File locateHostsFile() {
302 File hostsFile;
303 if (PlatformDependent.isWindows()) {
304 hostsFile = new File(System.getenv("SystemRoot") + WINDOWS_HOSTS_FILE_RELATIVE_PATH);
305 if (!hostsFile.exists()) {
306 hostsFile = new File(WINDOWS_DEFAULT_SYSTEM_ROOT + WINDOWS_HOSTS_FILE_RELATIVE_PATH);
307 }
308 } else {
309 hostsFile = new File(X_PLATFORMS_HOSTS_FILE_PATH);
310 }
311 return hostsFile;
312 }
313 }
314 }