diff --git a/src/main/java/JCurl.java b/src/main/java/JCurl.java index 3ef5121..d258a78 100644 --- a/src/main/java/JCurl.java +++ b/src/main/java/JCurl.java @@ -43,6 +43,8 @@ public static void main(String[] args) throws Exception { final OptionSpec headerSpec; @NonNull final OptionSpec engineOptionSpec; + @NonNull + final OptionSpec hostnamesSpec; public JCurl() { parser = new OptionParser(); @@ -67,6 +69,9 @@ public JCurl() { ) .withRequiredArg() .ofType(String.class); + hostnamesSpec = parser.acceptsAll(asList("hostnames", "n"), "hostnames /etc/hosts") + .withRequiredArg() + .ofType(String.class); } public ResponseEntity execute(String... args) throws Exception { @@ -108,6 +113,12 @@ public ResponseEntity execute(String... args) throws Exception { } } } + + if (optionSet.has("hostnames")) { + String hostnames = optionSet.valueOf(hostnamesSpec); + JCurlNameService.setHostNames(hostnames); + System.setProperty("sun.net.spi.nameservice.provider.1", "dns,mine"); + } return engineType.getEngine().submit(options); } diff --git a/src/main/java/JCurlNameService.java b/src/main/java/JCurlNameService.java new file mode 100644 index 0000000..980a30a --- /dev/null +++ b/src/main/java/JCurlNameService.java @@ -0,0 +1,103 @@ +import java.io.File; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; + +import io.netty.resolver.HostsFileEntries; +import io.netty.resolver.HostsFileParser; +import sun.net.spi.nameservice.NameService; + +public class JCurlNameService implements NameService { + private static String hostNames; + private HostsFileEntries systemEntries; + private HostsFileEntries customEntries; + private Map s4; + private Map s6; + private Map c4; + private Map c6; + + public static void setHostNames(String hostNames) { + JCurlNameService.hostNames = hostNames; + } + + public JCurlNameService() throws Exception { + HostsFileEntries hfe; + if (hostNames == null) { + hfe = new HostsFileEntries(new HashMap(), new HashMap()); + } else { + hfe = HostsFileParser.parse(new File(hostNames)); + } + this.init(hfe); + } + + public void init(HostsFileEntries customEntries) { + systemEntries = HostsFileParser.parseSilently(); + this.customEntries = customEntries; + s4 = invert4(systemEntries.inet4Entries()); + s6 = invert6(systemEntries.inet6Entries()); + c4 = invert4(customEntries.inet4Entries()); + c6 = invert6(customEntries.inet6Entries()); + } + + // Fixme: Handle duplicate Inet4Addresses? + private Map invert4(Map m) { + Map n = new HashMap<>(); + for (Entry e: m.entrySet()) { + n.put(e.getValue(), e.getKey()); + } + return n; + } + + // Fixme: Handle duplicate Inet6Addresses? + private Map invert6(Map m) { + Map n = new HashMap<>(); + for (Entry e: m.entrySet()) { + n.put(e.getValue(), e.getKey()); + } + return n; + } + + private InetAddress lookup(String host, HostsFileEntries he) { + InetAddress ia = this.customEntries.inet4Entries().get(host); + if (ia == null) { + ia = this.customEntries.inet6Entries().get(host); + } + return ia; + } + public InetAddress[] lookupAllHostAddr(String host) throws UnknownHostException { + InetAddress ia = lookup(host, customEntries); + if (ia == null) { + ia = lookup(host, systemEntries); + } + if (ia == null) { + throw new UnknownHostException(host); + } + return new InetAddress[]{ia}; + } + + public String getHostByAddr(byte[] addr) throws UnknownHostException { + InetAddress ia = InetAddress.getByAddress(addr); + String host = c4.get(ia); + if (host == null) { + host = c6.get(ia); + } + if (host == null) { + host = s4.get(ia); + } + if (host == null) { + host = s6.get(ia); + } + if (host == null) { + host = ia.getHostName(); + } + if (host == null) { + throw new UnknownHostException(Arrays.toString(addr)); + } + return host; + } +} diff --git a/src/main/java/JCurlNameServiceDescriptor.java b/src/main/java/JCurlNameServiceDescriptor.java new file mode 100644 index 0000000..d89f9a2 --- /dev/null +++ b/src/main/java/JCurlNameServiceDescriptor.java @@ -0,0 +1,18 @@ +/** + * + */ +import sun.net.spi.nameservice.*; + +public final class JCurlNameServiceDescriptor implements NameServiceDescriptor { + public NameService createNameService() throws Exception { + return new JCurlNameService(); + } + + public String getProviderName() { + return "mine"; + } + + public String getType() { + return "dns"; + } +} diff --git a/src/main/java/io/netty/resolver/HostsFileEntries.java b/src/main/java/io/netty/resolver/HostsFileEntries.java new file mode 100644 index 0000000..0fd8219 --- /dev/null +++ b/src/main/java/io/netty/resolver/HostsFileEntries.java @@ -0,0 +1,63 @@ +/* + * Copyright 2017 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: + * + * http://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.internal.UnstableApi; + +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +/** + * A container of hosts file entries + */ +//@UnstableApi +public final class HostsFileEntries { + + /** + * Empty entries + */ + static final HostsFileEntries EMPTY = + new HostsFileEntries( + Collections.emptyMap(), + Collections.emptyMap()); + + private final Map inet4Entries; + private final Map inet6Entries; + + public HostsFileEntries(Map inet4Entries, Map inet6Entries) { + this.inet4Entries = Collections.unmodifiableMap(new HashMap(inet4Entries)); + this.inet6Entries = Collections.unmodifiableMap(new HashMap(inet6Entries)); + } + + /** + * The IPv4 entries + * @return the IPv4 entries + */ + public Map inet4Entries() { + return inet4Entries; + } + + /** + * The IPv6 entries + * @return the IPv6 entries + */ + public Map inet6Entries() { + return inet6Entries; + } +} diff --git a/src/main/java/io/netty/resolver/HostsFileParser.java b/src/main/java/io/netty/resolver/HostsFileParser.java new file mode 100644 index 0000000..f554fb5 --- /dev/null +++ b/src/main/java/io/netty/resolver/HostsFileParser.java @@ -0,0 +1,193 @@ +/* + * Copyright 2015 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: + * + * http://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.UnstableApi; +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.FileReader; +import java.io.IOException; +import java.io.Reader; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; +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. + */ +//@UnstableApi +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. + * + * @return a {@link HostsFileEntries} + */ + public static HostsFileEntries parseSilently() { + File hostsFile = locateHostsFile(); + try { + return parse(hostsFile); + } catch (IOException e) { + logger.warn("Failed to load and parse hosts file at " + hostsFile.getPath(), e); + return HostsFileEntries.EMPTY; + } + } + + /** + * Parse hosts file at standard OS location. + * + * @return a {@link HostsFileEntries} + * @throws IOException file could not be read + */ + public static HostsFileEntries parse() throws IOException { + return parse(locateHostsFile()); + } + + /** + * Parse a hosts file. + * + * @param file the file to be parsed + * @return a {@link HostsFileEntries} + * @throws IOException file could not be read + */ + public static HostsFileEntries parse(File file) throws IOException { + checkNotNull(file, "file"); + if (file.exists() && file.isFile()) { + return parse(new BufferedReader(new FileReader(file))); + } else { + return HostsFileEntries.EMPTY; + } + } + + /** + * Parse a reader of hosts file format. + * + * @param reader the file to be parsed + * @return a {@link HostsFileEntries} + * @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 ipv4Entries = new HashMap(); + Map ipv6Entries = new HashMap(); + 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 lineParts = new ArrayList(); + 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); + } + } + } + + /** + * Can't be instantiated. + */ + private HostsFileParser() { + } +} diff --git a/src/main/resources/META-INF/services/sun.net.spi.nameservice.NameServiceDescriptor b/src/main/resources/META-INF/services/sun.net.spi.nameservice.NameServiceDescriptor new file mode 100644 index 0000000..c17ba5e --- /dev/null +++ b/src/main/resources/META-INF/services/sun.net.spi.nameservice.NameServiceDescriptor @@ -0,0 +1,2 @@ +# dns service provider descriptor +JCurlNameServiceDescriptor