Add DefaultHostsFileEntriesResolver#addresses to provide all hosts file's entries for a hostname (#11246)
Motivation: DefaultHostsFileEntriesResolver should provide all hosts file's entries for a hostname when DnsNameResolver#resolveAll as opposed to the current implementation where only the first entry is taken into consideration Modification: - Add DefaultHostsFileEntriesResolver#addresses to provide all hosts file's entries for a hostname - Add HostsFileEntriesProvider to provide all hosts file's entries for a hostname and to keep backwards compatibility for HostsFileEntries and HostsFileParser - DnsNameResolver#resolveAll uses the new DefaultHostsFileEntriesResolver#addresses - BlockHound configuration: replace HostsFileParser#parse with HostsFileEntriesProvider$ParserImpl#parse as the latter does the parsing - Add junit tests Result: Fixes #10834 * Address feedback
This commit is contained in:
parent
11e6a77fba
commit
fa8f7a3510
@ -128,7 +128,7 @@ class Hidden {
|
||||
"parseEtcResolverOptions");
|
||||
|
||||
builder.allowBlockingCallsInside(
|
||||
"io.netty.resolver.HostsFileParser",
|
||||
"io.netty.resolver.HostsFileEntriesProvider$ParserImpl",
|
||||
"parse");
|
||||
|
||||
builder.nonBlockingThreadPredicate(new Function<Predicate<Thread>, Predicate<Thread>>() {
|
||||
|
@ -46,6 +46,7 @@ import io.netty.handler.codec.dns.DnsRecordType;
|
||||
import io.netty.handler.codec.dns.DnsResponse;
|
||||
import io.netty.handler.codec.dns.TcpDnsQueryEncoder;
|
||||
import io.netty.handler.codec.dns.TcpDnsResponseDecoder;
|
||||
import io.netty.resolver.DefaultHostsFileEntriesResolver;
|
||||
import io.netty.resolver.HostsFileEntries;
|
||||
import io.netty.resolver.HostsFileEntriesResolver;
|
||||
import io.netty.resolver.InetNameResolver;
|
||||
@ -693,20 +694,38 @@ public class DnsNameResolver extends InetNameResolver {
|
||||
private InetAddress resolveHostsFileEntry(String hostname) {
|
||||
if (hostsFileEntriesResolver == null) {
|
||||
return null;
|
||||
}
|
||||
InetAddress address = hostsFileEntriesResolver.address(hostname, resolvedAddressTypes);
|
||||
return address == null && isLocalWindowsHost(hostname) ? LOCALHOST_ADDRESS : address;
|
||||
}
|
||||
|
||||
private List<InetAddress> resolveHostsFileEntries(String hostname) {
|
||||
if (hostsFileEntriesResolver == null) {
|
||||
return null;
|
||||
}
|
||||
List<InetAddress> addresses;
|
||||
if (hostsFileEntriesResolver instanceof DefaultHostsFileEntriesResolver) {
|
||||
addresses = ((DefaultHostsFileEntriesResolver) hostsFileEntriesResolver)
|
||||
.addresses(hostname, resolvedAddressTypes);
|
||||
} else {
|
||||
InetAddress address = hostsFileEntriesResolver.address(hostname, resolvedAddressTypes);
|
||||
if (address == null && PlatformDependent.isWindows() &&
|
||||
(LOCALHOST.equalsIgnoreCase(hostname) ||
|
||||
(WINDOWS_HOST_NAME != null && WINDOWS_HOST_NAME.equalsIgnoreCase(hostname)))) {
|
||||
// If we tried to resolve localhost we need workaround that windows removed localhost from its
|
||||
// hostfile in later versions.
|
||||
// See https://github.com/netty/netty/issues/5386
|
||||
// Need a workaround for resolving the host (computer) name in case it cannot be resolved from hostfile
|
||||
// See https://github.com/netty/netty/issues/11142
|
||||
return LOCALHOST_ADDRESS;
|
||||
}
|
||||
return address;
|
||||
addresses = address != null ? Collections.singletonList(address) : null;
|
||||
}
|
||||
return addresses == null && isLocalWindowsHost(hostname) ?
|
||||
Collections.singletonList(LOCALHOST_ADDRESS) : addresses;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the given hostname is the localhost/host (computer) name on Windows OS.
|
||||
* Windows OS removed the localhost/host (computer) name information from the hosts file in the later versions
|
||||
* and such hostname cannot be resolved from hosts file.
|
||||
* See https://github.com/netty/netty/issues/5386
|
||||
* See https://github.com/netty/netty/issues/11142
|
||||
*/
|
||||
private static boolean isLocalWindowsHost(String hostname) {
|
||||
return PlatformDependent.isWindows() &&
|
||||
(LOCALHOST.equalsIgnoreCase(hostname) ||
|
||||
(WINDOWS_HOST_NAME != null && WINDOWS_HOST_NAME.equalsIgnoreCase(hostname)));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -840,24 +859,29 @@ public class DnsNameResolver extends InetNameResolver {
|
||||
final String hostname = question.name();
|
||||
|
||||
if (type == DnsRecordType.A || type == DnsRecordType.AAAA) {
|
||||
final InetAddress hostsFileEntry = resolveHostsFileEntry(hostname);
|
||||
if (hostsFileEntry != null) {
|
||||
ByteBuf content = null;
|
||||
if (hostsFileEntry instanceof Inet4Address) {
|
||||
if (type == DnsRecordType.A) {
|
||||
content = Unpooled.wrappedBuffer(hostsFileEntry.getAddress());
|
||||
final List<InetAddress> hostsFileEntries = resolveHostsFileEntries(hostname);
|
||||
if (hostsFileEntries != null) {
|
||||
List<DnsRecord> result = new ArrayList<DnsRecord>();
|
||||
for (InetAddress hostsFileEntry : hostsFileEntries) {
|
||||
ByteBuf content = null;
|
||||
if (hostsFileEntry instanceof Inet4Address) {
|
||||
if (type == DnsRecordType.A) {
|
||||
content = Unpooled.wrappedBuffer(hostsFileEntry.getAddress());
|
||||
}
|
||||
} else if (hostsFileEntry instanceof Inet6Address) {
|
||||
if (type == DnsRecordType.AAAA) {
|
||||
content = Unpooled.wrappedBuffer(hostsFileEntry.getAddress());
|
||||
}
|
||||
}
|
||||
} else if (hostsFileEntry instanceof Inet6Address) {
|
||||
if (type == DnsRecordType.AAAA) {
|
||||
content = Unpooled.wrappedBuffer(hostsFileEntry.getAddress());
|
||||
if (content != null) {
|
||||
// Our current implementation does not support reloading the hosts file,
|
||||
// so use a fairly large TTL (1 day, i.e. 86400 seconds).
|
||||
result.add(new DefaultDnsRawRecord(hostname, type, 86400, content));
|
||||
}
|
||||
}
|
||||
|
||||
if (content != null) {
|
||||
// Our current implementation does not support reloading the hosts file,
|
||||
// so use a fairly large TTL (1 day, i.e. 86400 seconds).
|
||||
trySuccess(promise, Collections.<DnsRecord>singletonList(
|
||||
new DefaultDnsRawRecord(hostname, type, 86400, content)));
|
||||
if (!result.isEmpty()) {
|
||||
trySuccess(promise, result);
|
||||
return promise;
|
||||
}
|
||||
}
|
||||
@ -1033,9 +1057,9 @@ public class DnsNameResolver extends InetNameResolver {
|
||||
|
||||
final String hostname = hostname(inetHost);
|
||||
|
||||
InetAddress hostsFileEntry = resolveHostsFileEntry(hostname);
|
||||
if (hostsFileEntry != null) {
|
||||
promise.setSuccess(Collections.singletonList(hostsFileEntry));
|
||||
List<InetAddress> hostsFileEntries = resolveHostsFileEntries(hostname);
|
||||
if (hostsFileEntries != null) {
|
||||
promise.setSuccess(hostsFileEntries);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -18,10 +18,10 @@ package io.netty.resolver;
|
||||
import io.netty.util.CharsetUtil;
|
||||
import io.netty.util.internal.PlatformDependent;
|
||||
|
||||
import java.net.Inet4Address;
|
||||
import java.net.Inet6Address;
|
||||
import java.net.InetAddress;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
@ -30,21 +30,47 @@ import java.util.Map;
|
||||
*/
|
||||
public final class DefaultHostsFileEntriesResolver implements HostsFileEntriesResolver {
|
||||
|
||||
private final Map<String, Inet4Address> inet4Entries;
|
||||
private final Map<String, Inet6Address> inet6Entries;
|
||||
private final Map<String, List<InetAddress>> inet4Entries;
|
||||
private final Map<String, List<InetAddress>> inet6Entries;
|
||||
|
||||
public DefaultHostsFileEntriesResolver() {
|
||||
this(parseEntries());
|
||||
}
|
||||
|
||||
// for testing purpose only
|
||||
DefaultHostsFileEntriesResolver(HostsFileEntries entries) {
|
||||
inet4Entries = entries.inet4Entries();
|
||||
inet6Entries = entries.inet6Entries();
|
||||
DefaultHostsFileEntriesResolver(HostsFileEntriesProvider entries) {
|
||||
inet4Entries = entries.ipv4Entries();
|
||||
inet6Entries = entries.ipv6Entries();
|
||||
}
|
||||
|
||||
@Override
|
||||
public InetAddress address(String inetHost, ResolvedAddressTypes resolvedAddressTypes) {
|
||||
String normalized = normalize(inetHost);
|
||||
switch (resolvedAddressTypes) {
|
||||
case IPV4_ONLY:
|
||||
return firstAddress(inet4Entries.get(normalized));
|
||||
case IPV6_ONLY:
|
||||
return firstAddress(inet6Entries.get(normalized));
|
||||
case IPV4_PREFERRED:
|
||||
InetAddress inet4Address = firstAddress(inet4Entries.get(normalized));
|
||||
return inet4Address != null ? inet4Address : firstAddress(inet6Entries.get(normalized));
|
||||
case IPV6_PREFERRED:
|
||||
InetAddress inet6Address = firstAddress(inet6Entries.get(normalized));
|
||||
return inet6Address != null ? inet6Address : firstAddress(inet4Entries.get(normalized));
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown ResolvedAddressTypes " + resolvedAddressTypes);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves all addresses of a hostname against the entries in a hosts file, depending on the specified
|
||||
* {@link ResolvedAddressTypes}.
|
||||
*
|
||||
* @param inetHost the hostname to resolve
|
||||
* @param resolvedAddressTypes the address types to resolve
|
||||
* @return all matching addresses or {@code null} in case the hostname cannot be resolved
|
||||
*/
|
||||
public List<InetAddress> addresses(String inetHost, ResolvedAddressTypes resolvedAddressTypes) {
|
||||
String normalized = normalize(inetHost);
|
||||
switch (resolvedAddressTypes) {
|
||||
case IPV4_ONLY:
|
||||
@ -52,11 +78,13 @@ public final class DefaultHostsFileEntriesResolver implements HostsFileEntriesRe
|
||||
case IPV6_ONLY:
|
||||
return inet6Entries.get(normalized);
|
||||
case IPV4_PREFERRED:
|
||||
Inet4Address inet4Address = inet4Entries.get(normalized);
|
||||
return inet4Address != null? inet4Address : inet6Entries.get(normalized);
|
||||
List<InetAddress> allInet4Addresses = inet4Entries.get(normalized);
|
||||
return allInet4Addresses != null ? allAddresses(allInet4Addresses, inet6Entries.get(normalized)) :
|
||||
inet6Entries.get(normalized);
|
||||
case IPV6_PREFERRED:
|
||||
Inet6Address inet6Address = inet6Entries.get(normalized);
|
||||
return inet6Address != null? inet6Address : inet4Entries.get(normalized);
|
||||
List<InetAddress> allInet6Addresses = inet6Entries.get(normalized);
|
||||
return allInet6Addresses != null ? allAddresses(allInet6Addresses, inet4Entries.get(normalized)) :
|
||||
inet4Entries.get(normalized);
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown ResolvedAddressTypes " + resolvedAddressTypes);
|
||||
}
|
||||
@ -67,13 +95,27 @@ public final class DefaultHostsFileEntriesResolver implements HostsFileEntriesRe
|
||||
return inetHost.toLowerCase(Locale.ENGLISH);
|
||||
}
|
||||
|
||||
private static HostsFileEntries parseEntries() {
|
||||
private static List<InetAddress> allAddresses(List<InetAddress> a, List<InetAddress> b) {
|
||||
List<InetAddress> result = new ArrayList<InetAddress>(a.size() + (b == null ? 0 : b.size()));
|
||||
result.addAll(a);
|
||||
if (b != null) {
|
||||
result.addAll(b);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static InetAddress firstAddress(List<InetAddress> addresses) {
|
||||
return addresses != null && !addresses.isEmpty() ? addresses.get(0) : null;
|
||||
}
|
||||
|
||||
private static HostsFileEntriesProvider parseEntries() {
|
||||
if (PlatformDependent.isWindows()) {
|
||||
// Ony windows there seems to be no standard for the encoding used for the hosts file, so let us
|
||||
// try multiple until we either were able to parse it or there is none left and so we return an
|
||||
// empty intstance.
|
||||
return HostsFileParser.parseSilently(Charset.defaultCharset(), CharsetUtil.UTF_16, CharsetUtil.UTF_8);
|
||||
// empty instance.
|
||||
return HostsFileEntriesProvider.parser()
|
||||
.parseSilently(Charset.defaultCharset(), CharsetUtil.UTF_16, CharsetUtil.UTF_8);
|
||||
}
|
||||
return HostsFileParser.parseSilently();
|
||||
return HostsFileEntriesProvider.parser().parseSilently();
|
||||
}
|
||||
}
|
||||
|
@ -22,7 +22,9 @@ import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A container of hosts file entries
|
||||
* A container of hosts file entries.
|
||||
* The mappings contain only the first entry per hostname.
|
||||
* Consider using {@link HostsFileEntriesProvider} when mappings with all entries per hostname are needed.
|
||||
*/
|
||||
public final class HostsFileEntries {
|
||||
|
||||
|
@ -0,0 +1,311 @@
|
||||
/*
|
||||
* Copyright 2021 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:
|
||||
*
|
||||
* https://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.
|
||||
*/
|
||||
package io.netty.resolver;
|
||||
|
||||
import io.netty.util.NetUtil;
|
||||
import io.netty.util.internal.PlatformDependent;
|
||||
import io.netty.util.internal.logging.InternalLogger;
|
||||
import io.netty.util.internal.logging.InternalLoggerFactory;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.net.Inet4Address;
|
||||
import java.net.InetAddress;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static io.netty.util.internal.ObjectUtil.checkNotNull;
|
||||
|
||||
/**
|
||||
* A container of hosts file entries
|
||||
*/
|
||||
public final class HostsFileEntriesProvider {
|
||||
|
||||
public interface Parser {
|
||||
|
||||
/**
|
||||
* Parses the hosts file at standard OS location using the system default {@link Charset} for decoding.
|
||||
*
|
||||
* @return a new {@link HostsFileEntriesProvider}
|
||||
* @throws IOException file could not be read
|
||||
*/
|
||||
HostsFileEntriesProvider parse() throws IOException;
|
||||
|
||||
/**
|
||||
* Parses the hosts file at standard OS location using the given {@link Charset}s one after another until
|
||||
* parse something or none is left.
|
||||
*
|
||||
* @param charsets the {@link Charset}s to try as file encodings when parsing
|
||||
* @return a new {@link HostsFileEntriesProvider}
|
||||
* @throws IOException file could not be read
|
||||
*/
|
||||
HostsFileEntriesProvider parse(Charset... charsets) throws IOException;
|
||||
|
||||
/**
|
||||
* Parses the provided hosts file using the given {@link Charset}s one after another until
|
||||
* parse something or none is left. In case {@link Charset}s are not provided,
|
||||
* the system default {@link Charset} is used for decoding.
|
||||
*
|
||||
* @param file the file to be parsed
|
||||
* @param charsets the {@link Charset}s to try as file encodings when parsing, in case {@link Charset}s
|
||||
* are not provided, the system default {@link Charset} is used for decoding
|
||||
* @return a new {@link HostsFileEntriesProvider}
|
||||
* @throws IOException file could not be read
|
||||
*/
|
||||
HostsFileEntriesProvider parse(File file, Charset... charsets) throws IOException;
|
||||
|
||||
/**
|
||||
* Performs the parsing operation using the provided reader of hosts file format.
|
||||
*
|
||||
* @param reader the reader of hosts file format
|
||||
* @return a new {@link HostsFileEntriesProvider}
|
||||
*/
|
||||
HostsFileEntriesProvider parse(Reader reader) throws IOException;
|
||||
|
||||
/**
|
||||
* Parses the hosts file at standard OS location using the system default {@link Charset} for decoding.
|
||||
*
|
||||
* @return a new {@link HostsFileEntriesProvider}
|
||||
*/
|
||||
HostsFileEntriesProvider parseSilently();
|
||||
|
||||
/**
|
||||
* Parses the hosts file at standard OS location using the given {@link Charset}s one after another until
|
||||
* parse something or none is left.
|
||||
*
|
||||
* @param charsets the {@link Charset}s to try as file encodings when parsing
|
||||
* @return a new {@link HostsFileEntriesProvider}
|
||||
*/
|
||||
HostsFileEntriesProvider parseSilently(Charset... charsets);
|
||||
|
||||
/**
|
||||
* Parses the provided hosts file using the given {@link Charset}s one after another until
|
||||
* parse something or none is left. In case {@link Charset}s are not provided,
|
||||
* the system default {@link Charset} is used for decoding.
|
||||
*
|
||||
* @param file the file to be parsed
|
||||
* @param charsets the {@link Charset}s to try as file encodings when parsing, in case {@link Charset}s
|
||||
* are not provided, the system default {@link Charset} is used for decoding
|
||||
* @return a new {@link HostsFileEntriesProvider}
|
||||
*/
|
||||
HostsFileEntriesProvider parseSilently(File file, Charset... charsets);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a parser for {@link HostsFileEntriesProvider}.
|
||||
*
|
||||
* @return a new {@link HostsFileEntriesProvider.Parser}
|
||||
*/
|
||||
public static Parser parser() {
|
||||
return new ParserImpl();
|
||||
}
|
||||
|
||||
static final HostsFileEntriesProvider EMPTY =
|
||||
new HostsFileEntriesProvider(
|
||||
Collections.<String, List<InetAddress>>emptyMap(),
|
||||
Collections.<String, List<InetAddress>>emptyMap());
|
||||
|
||||
private final Map<String, List<InetAddress>> ipv4Entries;
|
||||
private final Map<String, List<InetAddress>> ipv6Entries;
|
||||
|
||||
HostsFileEntriesProvider(Map<String, List<InetAddress>> ipv4Entries, Map<String, List<InetAddress>> ipv6Entries) {
|
||||
this.ipv4Entries = Collections.unmodifiableMap(new HashMap<String, List<InetAddress>>(ipv4Entries));
|
||||
this.ipv6Entries = Collections.unmodifiableMap(new HashMap<String, List<InetAddress>>(ipv6Entries));
|
||||
}
|
||||
|
||||
/**
|
||||
* The IPv4 entries.
|
||||
*
|
||||
* @return the IPv4 entries
|
||||
*/
|
||||
public Map<String, List<InetAddress>> ipv4Entries() {
|
||||
return ipv4Entries;
|
||||
}
|
||||
|
||||
/**
|
||||
* The IPv6 entries.
|
||||
*
|
||||
* @return the IPv6 entries
|
||||
*/
|
||||
public Map<String, List<InetAddress>> ipv6Entries() {
|
||||
return ipv6Entries;
|
||||
}
|
||||
|
||||
private static final class ParserImpl implements Parser {
|
||||
|
||||
private static final String WINDOWS_DEFAULT_SYSTEM_ROOT = "C:\\Windows";
|
||||
private static final String WINDOWS_HOSTS_FILE_RELATIVE_PATH = "\\system32\\drivers\\etc\\hosts";
|
||||
private static final String X_PLATFORMS_HOSTS_FILE_PATH = "/etc/hosts";
|
||||
|
||||
private static final Pattern WHITESPACES = Pattern.compile("[ \t]+");
|
||||
|
||||
private static final InternalLogger logger = InternalLoggerFactory.getInstance(Parser.class);
|
||||
|
||||
@Override
|
||||
public HostsFileEntriesProvider parse() throws IOException {
|
||||
return parse(locateHostsFile(), Charset.defaultCharset());
|
||||
}
|
||||
|
||||
@Override
|
||||
public HostsFileEntriesProvider parse(Charset... charsets) throws IOException {
|
||||
return parse(locateHostsFile(), charsets);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HostsFileEntriesProvider parse(File file, Charset... charsets) throws IOException {
|
||||
checkNotNull(file, "file");
|
||||
checkNotNull(charsets, "charsets");
|
||||
if (charsets.length == 0) {
|
||||
charsets = new Charset[]{Charset.defaultCharset()};
|
||||
}
|
||||
if (file.exists() && file.isFile()) {
|
||||
for (Charset charset : charsets) {
|
||||
BufferedReader reader = new BufferedReader(
|
||||
new InputStreamReader(new FileInputStream(file), charset));
|
||||
try {
|
||||
HostsFileEntriesProvider entries = parse(reader);
|
||||
if (entries != HostsFileEntriesProvider.EMPTY) {
|
||||
return entries;
|
||||
}
|
||||
} finally {
|
||||
reader.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
return HostsFileEntriesProvider.EMPTY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HostsFileEntriesProvider parse(Reader reader) throws IOException {
|
||||
checkNotNull(reader, "reader");
|
||||
BufferedReader buff = new BufferedReader(reader);
|
||||
try {
|
||||
Map<String, List<InetAddress>> ipv4Entries = new HashMap<String, List<InetAddress>>();
|
||||
Map<String, List<InetAddress>> ipv6Entries = new HashMap<String, List<InetAddress>>();
|
||||
String line;
|
||||
while ((line = buff.readLine()) != null) {
|
||||
// remove comment
|
||||
int commentPosition = line.indexOf('#');
|
||||
if (commentPosition != -1) {
|
||||
line = line.substring(0, commentPosition);
|
||||
}
|
||||
// skip empty lines
|
||||
line = line.trim();
|
||||
if (line.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// split
|
||||
List<String> lineParts = new ArrayList<String>();
|
||||
for (String s : WHITESPACES.split(line)) {
|
||||
if (!s.isEmpty()) {
|
||||
lineParts.add(s);
|
||||
}
|
||||
}
|
||||
|
||||
// a valid line should be [IP, hostname, alias*]
|
||||
if (lineParts.size() < 2) {
|
||||
// skip invalid line
|
||||
continue;
|
||||
}
|
||||
|
||||
byte[] ipBytes = NetUtil.createByteArrayFromIpAddressString(lineParts.get(0));
|
||||
|
||||
if (ipBytes == null) {
|
||||
// skip invalid IP
|
||||
continue;
|
||||
}
|
||||
|
||||
// loop over hostname and aliases
|
||||
for (int i = 1; i < lineParts.size(); i++) {
|
||||
String hostname = lineParts.get(i);
|
||||
String hostnameLower = hostname.toLowerCase(Locale.ENGLISH);
|
||||
InetAddress address = InetAddress.getByAddress(hostname, ipBytes);
|
||||
List<InetAddress> addresses;
|
||||
if (address instanceof Inet4Address) {
|
||||
addresses = ipv4Entries.get(hostnameLower);
|
||||
if (addresses == null) {
|
||||
addresses = new ArrayList<InetAddress>();
|
||||
ipv4Entries.put(hostnameLower, addresses);
|
||||
}
|
||||
} else {
|
||||
addresses = ipv6Entries.get(hostnameLower);
|
||||
if (addresses == null) {
|
||||
addresses = new ArrayList<InetAddress>();
|
||||
ipv6Entries.put(hostnameLower, addresses);
|
||||
}
|
||||
}
|
||||
addresses.add(address);
|
||||
}
|
||||
}
|
||||
return ipv4Entries.isEmpty() && ipv6Entries.isEmpty() ?
|
||||
HostsFileEntriesProvider.EMPTY :
|
||||
new HostsFileEntriesProvider(ipv4Entries, ipv6Entries);
|
||||
} finally {
|
||||
try {
|
||||
buff.close();
|
||||
} catch (IOException e) {
|
||||
logger.warn("Failed to close a reader", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public HostsFileEntriesProvider parseSilently() {
|
||||
return parseSilently(locateHostsFile(), Charset.defaultCharset());
|
||||
}
|
||||
|
||||
@Override
|
||||
public HostsFileEntriesProvider parseSilently(Charset... charsets) {
|
||||
return parseSilently(locateHostsFile(), charsets);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HostsFileEntriesProvider parseSilently(File file, Charset... charsets) {
|
||||
try {
|
||||
return parse(file, charsets);
|
||||
} catch (IOException e) {
|
||||
if (logger.isWarnEnabled()) {
|
||||
logger.warn("Failed to load and parse hosts file at " + file.getPath(), e);
|
||||
}
|
||||
return HostsFileEntriesProvider.EMPTY;
|
||||
}
|
||||
}
|
||||
|
||||
private static File locateHostsFile() {
|
||||
File hostsFile;
|
||||
if (PlatformDependent.isWindows()) {
|
||||
hostsFile = new File(System.getenv("SystemRoot") + WINDOWS_HOSTS_FILE_RELATIVE_PATH);
|
||||
if (!hostsFile.exists()) {
|
||||
hostsFile = new File(WINDOWS_DEFAULT_SYSTEM_ROOT + WINDOWS_HOSTS_FILE_RELATIVE_PATH);
|
||||
}
|
||||
} else {
|
||||
hostsFile = new File(X_PLATFORMS_HOSTS_FILE_PATH);
|
||||
}
|
||||
return hostsFile;
|
||||
}
|
||||
}
|
||||
}
|
@ -15,63 +15,31 @@
|
||||
*/
|
||||
package io.netty.resolver;
|
||||
|
||||
import io.netty.util.NetUtil;
|
||||
import io.netty.util.internal.PlatformDependent;
|
||||
import io.netty.util.internal.logging.InternalLogger;
|
||||
import io.netty.util.internal.logging.InternalLoggerFactory;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.net.Inet4Address;
|
||||
import java.net.Inet6Address;
|
||||
import java.net.InetAddress;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static io.netty.util.internal.ObjectUtil.*;
|
||||
|
||||
/**
|
||||
* A parser for hosts files.
|
||||
* The produced mappings contain only the first entry per hostname.
|
||||
* Consider using {@link HostsFileEntriesProvider} when mappings with all entries per hostname are needed.
|
||||
*/
|
||||
public final class HostsFileParser {
|
||||
|
||||
private static final String WINDOWS_DEFAULT_SYSTEM_ROOT = "C:\\Windows";
|
||||
private static final String WINDOWS_HOSTS_FILE_RELATIVE_PATH = "\\system32\\drivers\\etc\\hosts";
|
||||
private static final String X_PLATFORMS_HOSTS_FILE_PATH = "/etc/hosts";
|
||||
|
||||
private static final Pattern WHITESPACES = Pattern.compile("[ \t]+");
|
||||
|
||||
private static final InternalLogger logger = InternalLoggerFactory.getInstance(HostsFileParser.class);
|
||||
|
||||
private static File locateHostsFile() {
|
||||
File hostsFile;
|
||||
if (PlatformDependent.isWindows()) {
|
||||
hostsFile = new File(System.getenv("SystemRoot") + WINDOWS_HOSTS_FILE_RELATIVE_PATH);
|
||||
if (!hostsFile.exists()) {
|
||||
hostsFile = new File(WINDOWS_DEFAULT_SYSTEM_ROOT + WINDOWS_HOSTS_FILE_RELATIVE_PATH);
|
||||
}
|
||||
} else {
|
||||
hostsFile = new File(X_PLATFORMS_HOSTS_FILE_PATH);
|
||||
}
|
||||
return hostsFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse hosts file at standard OS location using the systems default {@link Charset} for decoding.
|
||||
*
|
||||
* @return a {@link HostsFileEntries}
|
||||
*/
|
||||
public static HostsFileEntries parseSilently() {
|
||||
return parseSilently(Charset.defaultCharset());
|
||||
return hostsFileEntries(HostsFileEntriesProvider.parser().parseSilently());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -82,15 +50,7 @@ public final class HostsFileParser {
|
||||
* @return a {@link HostsFileEntries}
|
||||
*/
|
||||
public static HostsFileEntries parseSilently(Charset... charsets) {
|
||||
File hostsFile = locateHostsFile();
|
||||
try {
|
||||
return parse(hostsFile, charsets);
|
||||
} catch (IOException e) {
|
||||
if (logger.isWarnEnabled()) {
|
||||
logger.warn("Failed to load and parse hosts file at " + hostsFile.getPath(), e);
|
||||
}
|
||||
return HostsFileEntries.EMPTY;
|
||||
}
|
||||
return hostsFileEntries(HostsFileEntriesProvider.parser().parseSilently(charsets));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -100,7 +60,7 @@ public final class HostsFileParser {
|
||||
* @throws IOException file could not be read
|
||||
*/
|
||||
public static HostsFileEntries parse() throws IOException {
|
||||
return parse(locateHostsFile());
|
||||
return hostsFileEntries(HostsFileEntriesProvider.parser().parse());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -111,7 +71,7 @@ public final class HostsFileParser {
|
||||
* @throws IOException file could not be read
|
||||
*/
|
||||
public static HostsFileEntries parse(File file) throws IOException {
|
||||
return parse(file, Charset.defaultCharset());
|
||||
return hostsFileEntries(HostsFileEntriesProvider.parser().parse(file));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -123,23 +83,7 @@ public final class HostsFileParser {
|
||||
* @throws IOException file could not be read
|
||||
*/
|
||||
public static HostsFileEntries parse(File file, Charset... charsets) throws IOException {
|
||||
checkNotNull(file, "file");
|
||||
checkNotNull(charsets, "charsets");
|
||||
if (file.exists() && file.isFile()) {
|
||||
for (Charset charset: charsets) {
|
||||
BufferedReader reader = new BufferedReader(
|
||||
new InputStreamReader(new FileInputStream(file), charset));
|
||||
try {
|
||||
HostsFileEntries entries = parse(reader);
|
||||
if (entries != HostsFileEntries.EMPTY) {
|
||||
return entries;
|
||||
}
|
||||
} finally {
|
||||
reader.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
return HostsFileEntries.EMPTY;
|
||||
return hostsFileEntries(HostsFileEntriesProvider.parser().parse(file, charsets));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -150,75 +94,7 @@ public final class HostsFileParser {
|
||||
* @throws IOException file could not be read
|
||||
*/
|
||||
public static HostsFileEntries parse(Reader reader) throws IOException {
|
||||
checkNotNull(reader, "reader");
|
||||
BufferedReader buff = new BufferedReader(reader);
|
||||
try {
|
||||
Map<String, Inet4Address> ipv4Entries = new HashMap<String, Inet4Address>();
|
||||
Map<String, Inet6Address> ipv6Entries = new HashMap<String, Inet6Address>();
|
||||
String line;
|
||||
while ((line = buff.readLine()) != null) {
|
||||
// remove comment
|
||||
int commentPosition = line.indexOf('#');
|
||||
if (commentPosition != -1) {
|
||||
line = line.substring(0, commentPosition);
|
||||
}
|
||||
// skip empty lines
|
||||
line = line.trim();
|
||||
if (line.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// split
|
||||
List<String> lineParts = new ArrayList<String>();
|
||||
for (String s: WHITESPACES.split(line)) {
|
||||
if (!s.isEmpty()) {
|
||||
lineParts.add(s);
|
||||
}
|
||||
}
|
||||
|
||||
// a valid line should be [IP, hostname, alias*]
|
||||
if (lineParts.size() < 2) {
|
||||
// skip invalid line
|
||||
continue;
|
||||
}
|
||||
|
||||
byte[] ipBytes = NetUtil.createByteArrayFromIpAddressString(lineParts.get(0));
|
||||
|
||||
if (ipBytes == null) {
|
||||
// skip invalid IP
|
||||
continue;
|
||||
}
|
||||
|
||||
// loop over hostname and aliases
|
||||
for (int i = 1; i < lineParts.size(); i ++) {
|
||||
String hostname = lineParts.get(i);
|
||||
String hostnameLower = hostname.toLowerCase(Locale.ENGLISH);
|
||||
InetAddress address = InetAddress.getByAddress(hostname, ipBytes);
|
||||
if (address instanceof Inet4Address) {
|
||||
Inet4Address previous = ipv4Entries.put(hostnameLower, (Inet4Address) address);
|
||||
if (previous != null) {
|
||||
// restore, we want to keep the first entry
|
||||
ipv4Entries.put(hostnameLower, previous);
|
||||
}
|
||||
} else {
|
||||
Inet6Address previous = ipv6Entries.put(hostnameLower, (Inet6Address) address);
|
||||
if (previous != null) {
|
||||
// restore, we want to keep the first entry
|
||||
ipv6Entries.put(hostnameLower, previous);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return ipv4Entries.isEmpty() && ipv6Entries.isEmpty() ?
|
||||
HostsFileEntries.EMPTY :
|
||||
new HostsFileEntries(ipv4Entries, ipv6Entries);
|
||||
} finally {
|
||||
try {
|
||||
buff.close();
|
||||
} catch (IOException e) {
|
||||
logger.warn("Failed to close a reader", e);
|
||||
}
|
||||
}
|
||||
return hostsFileEntries(HostsFileEntriesProvider.parser().parse(reader));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -226,4 +102,22 @@ public final class HostsFileParser {
|
||||
*/
|
||||
private HostsFileParser() {
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static HostsFileEntries hostsFileEntries(HostsFileEntriesProvider provider) {
|
||||
return provider == HostsFileEntriesProvider.EMPTY ? HostsFileEntries.EMPTY :
|
||||
new HostsFileEntries((Map<String, Inet4Address>) toMapWithSingleValue(provider.ipv4Entries()),
|
||||
(Map<String, Inet6Address>) toMapWithSingleValue(provider.ipv6Entries()));
|
||||
}
|
||||
|
||||
private static Map<String, ?> toMapWithSingleValue(Map<String, List<InetAddress>> fromMapWithListValue) {
|
||||
Map<String, InetAddress> result = new HashMap<String, InetAddress>();
|
||||
for (Map.Entry<String, List<InetAddress>> entry : fromMapWithListValue.entrySet()) {
|
||||
List<InetAddress> value = entry.getValue();
|
||||
if (!value.isEmpty()) {
|
||||
result.put(entry.getKey(), value.get(0));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
@ -22,9 +22,16 @@ import org.junit.Test;
|
||||
import java.net.Inet4Address;
|
||||
import java.net.Inet6Address;
|
||||
import java.net.InetAddress;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class DefaultHostsFileEntriesResolverTest {
|
||||
|
||||
/**
|
||||
@ -32,53 +39,103 @@ public class DefaultHostsFileEntriesResolverTest {
|
||||
* HostsFileParser tries to resolve hostnames as case-sensitive
|
||||
*/
|
||||
@Test
|
||||
public void testCaseInsensitivity() throws Exception {
|
||||
public void testCaseInsensitivity() {
|
||||
DefaultHostsFileEntriesResolver resolver = new DefaultHostsFileEntriesResolver();
|
||||
//normalized somehow
|
||||
Assert.assertEquals(resolver.normalize("localhost"), resolver.normalize("LOCALHOST"));
|
||||
assertEquals(resolver.normalize("localhost"), resolver.normalize("LOCALHOST"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldntFindWhenAddressTypeDoesntMatch() {
|
||||
Map<String, Inet4Address> inet4Entries = new HashMap<String, Inet4Address>();
|
||||
Map<String, Inet6Address> inet6Entries = new HashMap<String, Inet6Address>();
|
||||
Map<String, List<InetAddress>> inet4Entries = new HashMap<String, List<InetAddress>>();
|
||||
Map<String, List<InetAddress>> inet6Entries = new HashMap<String, List<InetAddress>>();
|
||||
|
||||
inet4Entries.put("localhost", NetUtil.LOCALHOST4);
|
||||
inet4Entries.put("localhost", Collections.<InetAddress>singletonList(NetUtil.LOCALHOST4));
|
||||
|
||||
DefaultHostsFileEntriesResolver resolver =
|
||||
new DefaultHostsFileEntriesResolver(new HostsFileEntries(inet4Entries, inet6Entries));
|
||||
new DefaultHostsFileEntriesResolver(new HostsFileEntriesProvider(inet4Entries, inet6Entries));
|
||||
|
||||
InetAddress address = resolver.address("localhost", ResolvedAddressTypes.IPV6_ONLY);
|
||||
Assert.assertNull("Should pick an IPv6 address", address);
|
||||
assertNull("Should pick an IPv6 address", address);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldPickIpv4WhenBothAreDefinedButIpv4IsPreferred() {
|
||||
Map<String, Inet4Address> inet4Entries = new HashMap<String, Inet4Address>();
|
||||
Map<String, Inet6Address> inet6Entries = new HashMap<String, Inet6Address>();
|
||||
Map<String, List<InetAddress>> inet4Entries = new HashMap<String, List<InetAddress>>();
|
||||
Map<String, List<InetAddress>> inet6Entries = new HashMap<String, List<InetAddress>>();
|
||||
|
||||
inet4Entries.put("localhost", NetUtil.LOCALHOST4);
|
||||
inet6Entries.put("localhost", NetUtil.LOCALHOST6);
|
||||
inet4Entries.put("localhost", Collections.<InetAddress>singletonList(NetUtil.LOCALHOST4));
|
||||
inet6Entries.put("localhost", Collections.<InetAddress>singletonList(NetUtil.LOCALHOST6));
|
||||
|
||||
DefaultHostsFileEntriesResolver resolver =
|
||||
new DefaultHostsFileEntriesResolver(new HostsFileEntries(inet4Entries, inet6Entries));
|
||||
new DefaultHostsFileEntriesResolver(new HostsFileEntriesProvider(inet4Entries, inet6Entries));
|
||||
|
||||
InetAddress address = resolver.address("localhost", ResolvedAddressTypes.IPV4_PREFERRED);
|
||||
Assert.assertTrue("Should pick an IPv4 address", address instanceof Inet4Address);
|
||||
assertTrue("Should pick an IPv4 address", address instanceof Inet4Address);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldPickIpv6WhenBothAreDefinedButIpv6IsPreferred() {
|
||||
Map<String, Inet4Address> inet4Entries = new HashMap<String, Inet4Address>();
|
||||
Map<String, Inet6Address> inet6Entries = new HashMap<String, Inet6Address>();
|
||||
Map<String, List<InetAddress>> inet4Entries = new HashMap<String, List<InetAddress>>();
|
||||
Map<String, List<InetAddress>> inet6Entries = new HashMap<String, List<InetAddress>>();
|
||||
|
||||
inet4Entries.put("localhost", NetUtil.LOCALHOST4);
|
||||
inet6Entries.put("localhost", NetUtil.LOCALHOST6);
|
||||
inet4Entries.put("localhost", Collections.<InetAddress>singletonList(NetUtil.LOCALHOST4));
|
||||
inet6Entries.put("localhost", Collections.<InetAddress>singletonList(NetUtil.LOCALHOST6));
|
||||
|
||||
DefaultHostsFileEntriesResolver resolver =
|
||||
new DefaultHostsFileEntriesResolver(new HostsFileEntries(inet4Entries, inet6Entries));
|
||||
new DefaultHostsFileEntriesResolver(new HostsFileEntriesProvider(inet4Entries, inet6Entries));
|
||||
|
||||
InetAddress address = resolver.address("localhost", ResolvedAddressTypes.IPV6_PREFERRED);
|
||||
Assert.assertTrue("Should pick an IPv6 address", address instanceof Inet6Address);
|
||||
assertTrue("Should pick an IPv6 address", address instanceof Inet6Address);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldntFindWhenAddressesTypeDoesntMatch() {
|
||||
Map<String, List<InetAddress>> inet4Entries = new HashMap<String, List<InetAddress>>();
|
||||
Map<String, List<InetAddress>> inet6Entries = new HashMap<String, List<InetAddress>>();
|
||||
|
||||
inet4Entries.put("localhost", Collections.<InetAddress>singletonList(NetUtil.LOCALHOST4));
|
||||
|
||||
DefaultHostsFileEntriesResolver resolver =
|
||||
new DefaultHostsFileEntriesResolver(new HostsFileEntriesProvider(inet4Entries, inet6Entries));
|
||||
|
||||
List<InetAddress> addresses = resolver.addresses("localhost", ResolvedAddressTypes.IPV6_ONLY);
|
||||
assertNull("Should pick an IPv6 address", addresses);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldPickIpv4FirstWhenBothAreDefinedButIpv4IsPreferred() {
|
||||
Map<String, List<InetAddress>> inet4Entries = new HashMap<String, List<InetAddress>>();
|
||||
Map<String, List<InetAddress>> inet6Entries = new HashMap<String, List<InetAddress>>();
|
||||
|
||||
inet4Entries.put("localhost", Collections.<InetAddress>singletonList(NetUtil.LOCALHOST4));
|
||||
inet6Entries.put("localhost", Collections.<InetAddress>singletonList(NetUtil.LOCALHOST6));
|
||||
|
||||
DefaultHostsFileEntriesResolver resolver =
|
||||
new DefaultHostsFileEntriesResolver(new HostsFileEntriesProvider(inet4Entries, inet6Entries));
|
||||
|
||||
List<InetAddress> addresses = resolver.addresses("localhost", ResolvedAddressTypes.IPV4_PREFERRED);
|
||||
assertNotNull(addresses);
|
||||
assertEquals(2, addresses.size());
|
||||
assertTrue("Should pick an IPv4 address", addresses.get(0) instanceof Inet4Address);
|
||||
assertTrue("Should pick an IPv6 address", addresses.get(1) instanceof Inet6Address);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldPickIpv6FirstWhenBothAreDefinedButIpv6IsPreferred() {
|
||||
Map<String, List<InetAddress>> inet4Entries = new HashMap<String, List<InetAddress>>();
|
||||
Map<String, List<InetAddress>> inet6Entries = new HashMap<String, List<InetAddress>>();
|
||||
|
||||
inet4Entries.put("localhost", Collections.<InetAddress>singletonList(NetUtil.LOCALHOST4));
|
||||
inet6Entries.put("localhost", Collections.<InetAddress>singletonList(NetUtil.LOCALHOST6));
|
||||
|
||||
DefaultHostsFileEntriesResolver resolver =
|
||||
new DefaultHostsFileEntriesResolver(new HostsFileEntriesProvider(inet4Entries, inet6Entries));
|
||||
|
||||
List<InetAddress> addresses = resolver.addresses("localhost", ResolvedAddressTypes.IPV6_PREFERRED);
|
||||
assertNotNull(addresses);
|
||||
assertEquals(2, addresses.size());
|
||||
assertTrue("Should pick an IPv6 address", addresses.get(0) instanceof Inet6Address);
|
||||
assertTrue("Should pick an IPv4 address", addresses.get(1) instanceof Inet4Address);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,146 @@
|
||||
/*
|
||||
* Copyright 2021 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:
|
||||
*
|
||||
* https://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.
|
||||
*/
|
||||
package io.netty.resolver;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.function.Executable;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.io.StringReader;
|
||||
import java.net.InetAddress;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
class HostsFileEntriesProviderTest {
|
||||
|
||||
@Test
|
||||
void testParse() throws IOException {
|
||||
String hostsString = new StringBuilder()
|
||||
.append("127.0.0.1 host1").append("\n") // single hostname, separated with blanks
|
||||
.append("::1 host1").append("\n") // same as above, but IPv6
|
||||
.append("\n") // empty line
|
||||
.append("192.168.0.1\thost2").append("\n") // single hostname, separated with tabs
|
||||
.append("#comment").append("\n") // comment at the beginning of the line
|
||||
.append(" #comment ").append("\n") // comment in the middle of the line
|
||||
.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 considered
|
||||
.append("192.168.0.5 HOST7").append("\n") // uppercase host, should match lowercase host
|
||||
.append("192.168.0.6 host7").append("\n") // must be considered
|
||||
.toString();
|
||||
|
||||
HostsFileEntriesProvider entries = HostsFileEntriesProvider.parser()
|
||||
.parse(new BufferedReader(new StringReader(hostsString)));
|
||||
Map<String, List<InetAddress>> inet4Entries = entries.ipv4Entries();
|
||||
Map<String, List<InetAddress>> inet6Entries = entries.ipv6Entries();
|
||||
|
||||
assertEquals(7, inet4Entries.size(), "Expected 7 IPv4 entries");
|
||||
assertEquals(1, inet6Entries.size(), "Expected 1 IPv6 entries");
|
||||
|
||||
assertEquals(1, inet4Entries.get("host1").size());
|
||||
assertEquals("127.0.0.1", inet4Entries.get("host1").get(0).getHostAddress());
|
||||
|
||||
assertEquals(1, inet4Entries.get("host2").size());
|
||||
assertEquals("192.168.0.1", inet4Entries.get("host2").get(0).getHostAddress());
|
||||
|
||||
assertEquals(1, inet4Entries.get("host3").size());
|
||||
assertEquals("192.168.0.2", inet4Entries.get("host3").get(0).getHostAddress());
|
||||
|
||||
assertEquals(2, inet4Entries.get("host4").size());
|
||||
assertEquals("192.168.0.3", inet4Entries.get("host4").get(0).getHostAddress());
|
||||
assertEquals("192.168.0.4", inet4Entries.get("host4").get(1).getHostAddress());
|
||||
|
||||
assertEquals(1, inet4Entries.get("host5").size());
|
||||
assertEquals("192.168.0.3", inet4Entries.get("host5").get(0).getHostAddress());
|
||||
|
||||
assertEquals(1, inet4Entries.get("host6").size());
|
||||
assertEquals("192.168.0.3", inet4Entries.get("host6").get(0).getHostAddress());
|
||||
|
||||
assertNotNull(inet4Entries.get("host7"), "Uppercase host doesn't resolve");
|
||||
assertEquals(2, inet4Entries.get("host7").size());
|
||||
assertEquals("192.168.0.5", inet4Entries.get("host7").get(0).getHostAddress());
|
||||
assertEquals("192.168.0.6", inet4Entries.get("host7").get(1).getHostAddress());
|
||||
|
||||
assertEquals(1, inet6Entries.get("host1").size());
|
||||
assertEquals("0:0:0:0:0:0:0:1", inet6Entries.get("host1").get(0).getHostAddress());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCharsetInputValidation() {
|
||||
assertThrows(NullPointerException.class, new Executable() {
|
||||
@Override
|
||||
public void execute() throws IOException {
|
||||
HostsFileEntriesProvider.parser().parse((Charset[]) null);
|
||||
}
|
||||
});
|
||||
|
||||
assertThrows(NullPointerException.class, new Executable() {
|
||||
@Override
|
||||
public void execute() throws IOException {
|
||||
HostsFileEntriesProvider.parser().parse(new File(""), (Charset[]) null);
|
||||
}
|
||||
});
|
||||
|
||||
assertThrows(NullPointerException.class, new Executable() {
|
||||
@Override
|
||||
public void execute() {
|
||||
HostsFileEntriesProvider.parser().parseSilently((Charset[]) null);
|
||||
}
|
||||
});
|
||||
|
||||
assertThrows(NullPointerException.class, new Executable() {
|
||||
@Override
|
||||
public void execute() {
|
||||
HostsFileEntriesProvider.parser().parseSilently(new File(""), (Charset[]) null);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFileInputValidation() {
|
||||
assertThrows(NullPointerException.class, new Executable() {
|
||||
@Override
|
||||
public void execute() throws IOException {
|
||||
HostsFileEntriesProvider.parser().parse((File) null);
|
||||
}
|
||||
});
|
||||
|
||||
assertThrows(NullPointerException.class, new Executable() {
|
||||
@Override
|
||||
public void execute() {
|
||||
HostsFileEntriesProvider.parser().parseSilently((File) null);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testReaderInputValidation() {
|
||||
assertThrows(NullPointerException.class, new Executable() {
|
||||
@Override
|
||||
public void execute() throws IOException {
|
||||
HostsFileEntriesProvider.parser().parse((Reader) null);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user