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");
|
"parseEtcResolverOptions");
|
||||||
|
|
||||||
builder.allowBlockingCallsInside(
|
builder.allowBlockingCallsInside(
|
||||||
"io.netty.resolver.HostsFileParser",
|
"io.netty.resolver.HostsFileEntriesProvider$ParserImpl",
|
||||||
"parse");
|
"parse");
|
||||||
|
|
||||||
builder.nonBlockingThreadPredicate(new Function<Predicate<Thread>, Predicate<Thread>>() {
|
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.DnsResponse;
|
||||||
import io.netty.handler.codec.dns.TcpDnsQueryEncoder;
|
import io.netty.handler.codec.dns.TcpDnsQueryEncoder;
|
||||||
import io.netty.handler.codec.dns.TcpDnsResponseDecoder;
|
import io.netty.handler.codec.dns.TcpDnsResponseDecoder;
|
||||||
|
import io.netty.resolver.DefaultHostsFileEntriesResolver;
|
||||||
import io.netty.resolver.HostsFileEntries;
|
import io.netty.resolver.HostsFileEntries;
|
||||||
import io.netty.resolver.HostsFileEntriesResolver;
|
import io.netty.resolver.HostsFileEntriesResolver;
|
||||||
import io.netty.resolver.InetNameResolver;
|
import io.netty.resolver.InetNameResolver;
|
||||||
@ -693,20 +694,38 @@ public class DnsNameResolver extends InetNameResolver {
|
|||||||
private InetAddress resolveHostsFileEntry(String hostname) {
|
private InetAddress resolveHostsFileEntry(String hostname) {
|
||||||
if (hostsFileEntriesResolver == null) {
|
if (hostsFileEntriesResolver == null) {
|
||||||
return 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 {
|
} else {
|
||||||
InetAddress address = hostsFileEntriesResolver.address(hostname, resolvedAddressTypes);
|
InetAddress address = hostsFileEntriesResolver.address(hostname, resolvedAddressTypes);
|
||||||
if (address == null && PlatformDependent.isWindows() &&
|
addresses = address != null ? Collections.singletonList(address) : null;
|
||||||
(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;
|
|
||||||
}
|
}
|
||||||
|
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();
|
final String hostname = question.name();
|
||||||
|
|
||||||
if (type == DnsRecordType.A || type == DnsRecordType.AAAA) {
|
if (type == DnsRecordType.A || type == DnsRecordType.AAAA) {
|
||||||
final InetAddress hostsFileEntry = resolveHostsFileEntry(hostname);
|
final List<InetAddress> hostsFileEntries = resolveHostsFileEntries(hostname);
|
||||||
if (hostsFileEntry != null) {
|
if (hostsFileEntries != null) {
|
||||||
ByteBuf content = null;
|
List<DnsRecord> result = new ArrayList<DnsRecord>();
|
||||||
if (hostsFileEntry instanceof Inet4Address) {
|
for (InetAddress hostsFileEntry : hostsFileEntries) {
|
||||||
if (type == DnsRecordType.A) {
|
ByteBuf content = null;
|
||||||
content = Unpooled.wrappedBuffer(hostsFileEntry.getAddress());
|
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 (content != null) {
|
||||||
if (type == DnsRecordType.AAAA) {
|
// Our current implementation does not support reloading the hosts file,
|
||||||
content = Unpooled.wrappedBuffer(hostsFileEntry.getAddress());
|
// so use a fairly large TTL (1 day, i.e. 86400 seconds).
|
||||||
|
result.add(new DefaultDnsRawRecord(hostname, type, 86400, content));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (content != null) {
|
if (!result.isEmpty()) {
|
||||||
// Our current implementation does not support reloading the hosts file,
|
trySuccess(promise, result);
|
||||||
// so use a fairly large TTL (1 day, i.e. 86400 seconds).
|
|
||||||
trySuccess(promise, Collections.<DnsRecord>singletonList(
|
|
||||||
new DefaultDnsRawRecord(hostname, type, 86400, content)));
|
|
||||||
return promise;
|
return promise;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1033,9 +1057,9 @@ public class DnsNameResolver extends InetNameResolver {
|
|||||||
|
|
||||||
final String hostname = hostname(inetHost);
|
final String hostname = hostname(inetHost);
|
||||||
|
|
||||||
InetAddress hostsFileEntry = resolveHostsFileEntry(hostname);
|
List<InetAddress> hostsFileEntries = resolveHostsFileEntries(hostname);
|
||||||
if (hostsFileEntry != null) {
|
if (hostsFileEntries != null) {
|
||||||
promise.setSuccess(Collections.singletonList(hostsFileEntry));
|
promise.setSuccess(hostsFileEntries);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,10 +18,10 @@ package io.netty.resolver;
|
|||||||
import io.netty.util.CharsetUtil;
|
import io.netty.util.CharsetUtil;
|
||||||
import io.netty.util.internal.PlatformDependent;
|
import io.netty.util.internal.PlatformDependent;
|
||||||
|
|
||||||
import java.net.Inet4Address;
|
|
||||||
import java.net.Inet6Address;
|
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@ -30,21 +30,47 @@ import java.util.Map;
|
|||||||
*/
|
*/
|
||||||
public final class DefaultHostsFileEntriesResolver implements HostsFileEntriesResolver {
|
public final class DefaultHostsFileEntriesResolver implements HostsFileEntriesResolver {
|
||||||
|
|
||||||
private final Map<String, Inet4Address> inet4Entries;
|
private final Map<String, List<InetAddress>> inet4Entries;
|
||||||
private final Map<String, Inet6Address> inet6Entries;
|
private final Map<String, List<InetAddress>> inet6Entries;
|
||||||
|
|
||||||
public DefaultHostsFileEntriesResolver() {
|
public DefaultHostsFileEntriesResolver() {
|
||||||
this(parseEntries());
|
this(parseEntries());
|
||||||
}
|
}
|
||||||
|
|
||||||
// for testing purpose only
|
// for testing purpose only
|
||||||
DefaultHostsFileEntriesResolver(HostsFileEntries entries) {
|
DefaultHostsFileEntriesResolver(HostsFileEntriesProvider entries) {
|
||||||
inet4Entries = entries.inet4Entries();
|
inet4Entries = entries.ipv4Entries();
|
||||||
inet6Entries = entries.inet6Entries();
|
inet6Entries = entries.ipv6Entries();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public InetAddress address(String inetHost, ResolvedAddressTypes resolvedAddressTypes) {
|
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);
|
String normalized = normalize(inetHost);
|
||||||
switch (resolvedAddressTypes) {
|
switch (resolvedAddressTypes) {
|
||||||
case IPV4_ONLY:
|
case IPV4_ONLY:
|
||||||
@ -52,11 +78,13 @@ public final class DefaultHostsFileEntriesResolver implements HostsFileEntriesRe
|
|||||||
case IPV6_ONLY:
|
case IPV6_ONLY:
|
||||||
return inet6Entries.get(normalized);
|
return inet6Entries.get(normalized);
|
||||||
case IPV4_PREFERRED:
|
case IPV4_PREFERRED:
|
||||||
Inet4Address inet4Address = inet4Entries.get(normalized);
|
List<InetAddress> allInet4Addresses = inet4Entries.get(normalized);
|
||||||
return inet4Address != null? inet4Address : inet6Entries.get(normalized);
|
return allInet4Addresses != null ? allAddresses(allInet4Addresses, inet6Entries.get(normalized)) :
|
||||||
|
inet6Entries.get(normalized);
|
||||||
case IPV6_PREFERRED:
|
case IPV6_PREFERRED:
|
||||||
Inet6Address inet6Address = inet6Entries.get(normalized);
|
List<InetAddress> allInet6Addresses = inet6Entries.get(normalized);
|
||||||
return inet6Address != null? inet6Address : inet4Entries.get(normalized);
|
return allInet6Addresses != null ? allAddresses(allInet6Addresses, inet4Entries.get(normalized)) :
|
||||||
|
inet4Entries.get(normalized);
|
||||||
default:
|
default:
|
||||||
throw new IllegalArgumentException("Unknown ResolvedAddressTypes " + resolvedAddressTypes);
|
throw new IllegalArgumentException("Unknown ResolvedAddressTypes " + resolvedAddressTypes);
|
||||||
}
|
}
|
||||||
@ -67,13 +95,27 @@ public final class DefaultHostsFileEntriesResolver implements HostsFileEntriesRe
|
|||||||
return inetHost.toLowerCase(Locale.ENGLISH);
|
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()) {
|
if (PlatformDependent.isWindows()) {
|
||||||
// Ony windows there seems to be no standard for the encoding used for the hosts file, so let us
|
// 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
|
// try multiple until we either were able to parse it or there is none left and so we return an
|
||||||
// empty intstance.
|
// empty instance.
|
||||||
return HostsFileParser.parseSilently(Charset.defaultCharset(), CharsetUtil.UTF_16, CharsetUtil.UTF_8);
|
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;
|
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 {
|
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;
|
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.File;
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.io.Reader;
|
import java.io.Reader;
|
||||||
import java.net.Inet4Address;
|
import java.net.Inet4Address;
|
||||||
import java.net.Inet6Address;
|
import java.net.Inet6Address;
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
import static io.netty.util.internal.ObjectUtil.*;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A parser for hosts files.
|
* 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 {
|
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.
|
* Parse hosts file at standard OS location using the systems default {@link Charset} for decoding.
|
||||||
*
|
*
|
||||||
* @return a {@link HostsFileEntries}
|
* @return a {@link HostsFileEntries}
|
||||||
*/
|
*/
|
||||||
public static HostsFileEntries parseSilently() {
|
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}
|
* @return a {@link HostsFileEntries}
|
||||||
*/
|
*/
|
||||||
public static HostsFileEntries parseSilently(Charset... charsets) {
|
public static HostsFileEntries parseSilently(Charset... charsets) {
|
||||||
File hostsFile = locateHostsFile();
|
return hostsFileEntries(HostsFileEntriesProvider.parser().parseSilently(charsets));
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -100,7 +60,7 @@ public final class HostsFileParser {
|
|||||||
* @throws IOException file could not be read
|
* @throws IOException file could not be read
|
||||||
*/
|
*/
|
||||||
public static HostsFileEntries parse() throws IOException {
|
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
|
* @throws IOException file could not be read
|
||||||
*/
|
*/
|
||||||
public static HostsFileEntries parse(File file) throws IOException {
|
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
|
* @throws IOException file could not be read
|
||||||
*/
|
*/
|
||||||
public static HostsFileEntries parse(File file, Charset... charsets) throws IOException {
|
public static HostsFileEntries parse(File file, Charset... charsets) throws IOException {
|
||||||
checkNotNull(file, "file");
|
return hostsFileEntries(HostsFileEntriesProvider.parser().parse(file, charsets));
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -150,75 +94,7 @@ public final class HostsFileParser {
|
|||||||
* @throws IOException file could not be read
|
* @throws IOException file could not be read
|
||||||
*/
|
*/
|
||||||
public static HostsFileEntries parse(Reader reader) throws IOException {
|
public static HostsFileEntries parse(Reader reader) throws IOException {
|
||||||
checkNotNull(reader, "reader");
|
return hostsFileEntries(HostsFileEntriesProvider.parser().parse(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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -226,4 +102,22 @@ public final class HostsFileParser {
|
|||||||
*/
|
*/
|
||||||
private 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.Inet4Address;
|
||||||
import java.net.Inet6Address;
|
import java.net.Inet6Address;
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
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 {
|
public class DefaultHostsFileEntriesResolverTest {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -32,53 +39,103 @@ public class DefaultHostsFileEntriesResolverTest {
|
|||||||
* HostsFileParser tries to resolve hostnames as case-sensitive
|
* HostsFileParser tries to resolve hostnames as case-sensitive
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testCaseInsensitivity() throws Exception {
|
public void testCaseInsensitivity() {
|
||||||
DefaultHostsFileEntriesResolver resolver = new DefaultHostsFileEntriesResolver();
|
DefaultHostsFileEntriesResolver resolver = new DefaultHostsFileEntriesResolver();
|
||||||
//normalized somehow
|
//normalized somehow
|
||||||
Assert.assertEquals(resolver.normalize("localhost"), resolver.normalize("LOCALHOST"));
|
assertEquals(resolver.normalize("localhost"), resolver.normalize("LOCALHOST"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void shouldntFindWhenAddressTypeDoesntMatch() {
|
public void shouldntFindWhenAddressTypeDoesntMatch() {
|
||||||
Map<String, Inet4Address> inet4Entries = new HashMap<String, Inet4Address>();
|
Map<String, List<InetAddress>> inet4Entries = new HashMap<String, List<InetAddress>>();
|
||||||
Map<String, Inet6Address> inet6Entries = new HashMap<String, Inet6Address>();
|
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 =
|
DefaultHostsFileEntriesResolver resolver =
|
||||||
new DefaultHostsFileEntriesResolver(new HostsFileEntries(inet4Entries, inet6Entries));
|
new DefaultHostsFileEntriesResolver(new HostsFileEntriesProvider(inet4Entries, inet6Entries));
|
||||||
|
|
||||||
InetAddress address = resolver.address("localhost", ResolvedAddressTypes.IPV6_ONLY);
|
InetAddress address = resolver.address("localhost", ResolvedAddressTypes.IPV6_ONLY);
|
||||||
Assert.assertNull("Should pick an IPv6 address", address);
|
assertNull("Should pick an IPv6 address", address);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void shouldPickIpv4WhenBothAreDefinedButIpv4IsPreferred() {
|
public void shouldPickIpv4WhenBothAreDefinedButIpv4IsPreferred() {
|
||||||
Map<String, Inet4Address> inet4Entries = new HashMap<String, Inet4Address>();
|
Map<String, List<InetAddress>> inet4Entries = new HashMap<String, List<InetAddress>>();
|
||||||
Map<String, Inet6Address> inet6Entries = new HashMap<String, Inet6Address>();
|
Map<String, List<InetAddress>> inet6Entries = new HashMap<String, List<InetAddress>>();
|
||||||
|
|
||||||
inet4Entries.put("localhost", NetUtil.LOCALHOST4);
|
inet4Entries.put("localhost", Collections.<InetAddress>singletonList(NetUtil.LOCALHOST4));
|
||||||
inet6Entries.put("localhost", NetUtil.LOCALHOST6);
|
inet6Entries.put("localhost", Collections.<InetAddress>singletonList(NetUtil.LOCALHOST6));
|
||||||
|
|
||||||
DefaultHostsFileEntriesResolver resolver =
|
DefaultHostsFileEntriesResolver resolver =
|
||||||
new DefaultHostsFileEntriesResolver(new HostsFileEntries(inet4Entries, inet6Entries));
|
new DefaultHostsFileEntriesResolver(new HostsFileEntriesProvider(inet4Entries, inet6Entries));
|
||||||
|
|
||||||
InetAddress address = resolver.address("localhost", ResolvedAddressTypes.IPV4_PREFERRED);
|
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
|
@Test
|
||||||
public void shouldPickIpv6WhenBothAreDefinedButIpv6IsPreferred() {
|
public void shouldPickIpv6WhenBothAreDefinedButIpv6IsPreferred() {
|
||||||
Map<String, Inet4Address> inet4Entries = new HashMap<String, Inet4Address>();
|
Map<String, List<InetAddress>> inet4Entries = new HashMap<String, List<InetAddress>>();
|
||||||
Map<String, Inet6Address> inet6Entries = new HashMap<String, Inet6Address>();
|
Map<String, List<InetAddress>> inet6Entries = new HashMap<String, List<InetAddress>>();
|
||||||
|
|
||||||
inet4Entries.put("localhost", NetUtil.LOCALHOST4);
|
inet4Entries.put("localhost", Collections.<InetAddress>singletonList(NetUtil.LOCALHOST4));
|
||||||
inet6Entries.put("localhost", NetUtil.LOCALHOST6);
|
inet6Entries.put("localhost", Collections.<InetAddress>singletonList(NetUtil.LOCALHOST6));
|
||||||
|
|
||||||
DefaultHostsFileEntriesResolver resolver =
|
DefaultHostsFileEntriesResolver resolver =
|
||||||
new DefaultHostsFileEntriesResolver(new HostsFileEntries(inet4Entries, inet6Entries));
|
new DefaultHostsFileEntriesResolver(new HostsFileEntriesProvider(inet4Entries, inet6Entries));
|
||||||
|
|
||||||
InetAddress address = resolver.address("localhost", ResolvedAddressTypes.IPV6_PREFERRED);
|
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