package com.theotherian.dns; import java.io.FileInputStream; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.StringTokenizer; import org.apache.commons.lang.SystemUtils; import org.apache.log4j.Logger; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Maps; /** * Reads the hosts file and parses it into a map. Set up to be platform independent. * */ final class HostsFileResolver { private static final Logger LOGGER = Logger.getLogger(HostsFileResolver.class); private static final HostsFileResolver instance = new HostsFileResolver(); private final Map<String, String> overrides; private HostsFileResolver() { this.overrides = ImmutableMap.copyOf(parseHostsFile()); } static Map<String, String> overrides() { return instance.overrides; } static boolean hasOverride(String hostname) { return instance.overrides.containsKey(hostname); } static String getOverride(String hostname) { return instance.overrides.get(hostname); } private Map<String, String> parseHostsFile() { Map<String, String> overrides = Maps.newHashMap(); final String hosts; if (SystemUtils.IS_OS_WINDOWS) { hosts = "c:/WINDOWS/system32/drivers/etc/hosts"; } else if (SystemUtils.IS_OS_UNIX) { hosts = "/etc/hosts"; } else { LOGGER.error("Unable to find /etc/hosts for " + SystemUtils.OS_NAME); hosts = "/etc/hosts"; } FileInputStream fis = null; try { fis = new FileInputStream(hosts); for (;;) { // read lines List<String> names = parseLine(fis); if (names == null || names.isEmpty()) { break; } String val = names.get(0); for (int i = 1; i < names.size(); i ++) { // insert entries String key = names.get(i).toLowerCase(); if (isIpv6(val)) { continue; } else { LOGGER.info("Found hosts mapping " + key + " -> " + val); overrides.put(key, val); } } // insert entries } // read lines } catch (IOException e) { LOGGER.warn("Unable to read " + hosts, e); } finally { if (fis != null) { try { fis.close(); } catch (IOException e) { } // ignore } } return overrides; } /** * Is this an IPv6 form of address? * @param token * @return */ @VisibleForTesting static boolean isIpv6(String token) { return token.contains("::"); } /** * Return a single non-trivial line from the hosts file * @param fis * @return null at end, else list of tokens * @throws IOException */ @VisibleForTesting static List<String> parseLine(FileInputStream fis) throws IOException { for (;;) { // Try to find a useful line // Load a physical line String line = readLine(fis); if (line == null) { // end condition return null; } // Tokenize it, but go to next physical line if empty or comment StringTokenizer st = new StringTokenizer(line, " \t\r\n"); if (!st.hasMoreTokens()) { continue; } String token = st.nextToken(); if (isCommentDelimiter(token)) { continue; } // At this point we have a nontrivial line. Adduce first token, // then add remaining tokens to list by scanning. ArrayList<String> result = new ArrayList<String>(); result.add(token); while (st.hasMoreTokens()) { // read until eol or comment token = st.nextToken(); if (isCommentDelimiter(token)) { break; } result.add(token); } // read until eol or comment // Error to have just one token on the line if (result.size() == 1) { LOGGER.error("Single-token line in hosts file: '" + line + "'"); continue; } // Success! return result; } // Try to find a useful line } /** * Does the given string represent the beginning of a comment? * * @param token * @return */ @VisibleForTesting static boolean isCommentDelimiter(String token) { char ch = token.charAt(0); return ch == ';' || ch == '#'; } /** * read a single uninterpreted line from the file * @param fis * @return * @throws IOException */ @VisibleForTesting static String readLine(FileInputStream fis) throws IOException { // Return null if at EOF and no line int ch = fis.read(); if (ch == -1) { return null; // EOF } // If EOL by itself, return empty line if (isEOL(ch)) { return ""; } // Build line StringBuilder sb = new StringBuilder(); sb.append((char)ch); for (;;) { ch = fis.read(); if (isEOL(ch)) { break; } sb.append((char)ch); } return sb.toString(); } /** * Is the given character an end of line delimiter? * @param ch * @return */ @VisibleForTesting static boolean isEOL(int ch) { return ch == -1 || ch == '\r' || ch == '\n'; } }