Java tutorial
/* * Twittnuker - Twitter client for Android * * Copyright (C) 2013-2014 vanita5 <mail@vanita5.de> * * This program incorporates a modified version of Twidere. * Copyright (C) 2012-2014 Mariotaku Lee <mariotaku.lee@gmail.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package de.vanita5.twittnuker.util.net; import static android.text.TextUtils.isEmpty; import android.content.Context; import android.content.SharedPreferences; import android.util.Log; import de.vanita5.twittnuker.Constants; import de.vanita5.twittnuker.util.HostsFileParser; import de.vanita5.twittnuker.util.Utils; import org.apache.http.conn.util.InetAddressUtilsHC4; import org.xbill.DNS.AAAARecord; import org.xbill.DNS.ARecord; import org.xbill.DNS.CNAMERecord; import org.xbill.DNS.DClass; import org.xbill.DNS.Message; import org.xbill.DNS.Name; import org.xbill.DNS.Record; import org.xbill.DNS.Resolver; import org.xbill.DNS.Section; import org.xbill.DNS.SimpleResolver; import org.xbill.DNS.Type; import twitter4j.http.HostAddressResolver; import java.io.IOException; import java.net.InetAddress; import java.util.LinkedHashMap; public class TwidereHostAddressResolver implements Constants, HostAddressResolver { private static final String RESOLVER_LOGTAG = "TwidereHostAddressResolver"; private static final String DEFAULT_DNS_SERVER_ADDRESS = ""; private final SharedPreferences mHostMapping, mPreferences; private final HostsFileParser mSystemHosts = new HostsFileParser(); private final HostCache mHostCache = new HostCache(512); private final boolean mLocalMappingOnly; private final String mDnsAddress; private Resolver mDns; public TwidereHostAddressResolver(final Context context) { this(context, false); } public TwidereHostAddressResolver(final Context context, final boolean local_only) { mHostMapping = context.getSharedPreferences(HOST_MAPPING_PREFERENCES_NAME, Context.MODE_PRIVATE); mPreferences = context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE); final String address = mPreferences.getString(KEY_DNS_SERVER, DEFAULT_DNS_SERVER_ADDRESS); mDnsAddress = isValidIpAddress(address) ? address : DEFAULT_DNS_SERVER_ADDRESS; mLocalMappingOnly = local_only; } public synchronized void removeCachedHost(final String host) { mHostCache.remove(host); } @Override public String resolve(final String host) throws IOException { if (host == null) return null; if (isValidIpAddress(host)) return null; // First, I'll try to load address cached. if (mHostCache.containsKey(host)) { if (Utils.isDebugBuild()) { Log.d(RESOLVER_LOGTAG, "Got cached address " + mHostCache.get(host) + " for host " + host); } return mHostCache.get(host); } // Then I'll try to load from custom host mapping. // Stupid way to find top domain, but really fast. if (mHostMapping.contains(host)) { final String mappedAddr = mHostMapping.getString(host, null); mHostCache.put(host, mappedAddr); if (Utils.isDebugBuild()) { Log.d(RESOLVER_LOGTAG, "Got mapped address " + mappedAddr + " for host " + host); } return mappedAddr; } mSystemHosts.reloadIfNeeded(); if (mSystemHosts.contains(host)) { final String hostAddr = mSystemHosts.getAddress(host); mHostCache.put(host, hostAddr); if (Utils.isDebugBuild()) { Log.d(RESOLVER_LOGTAG, "Got mapped address " + hostAddr + " for host " + host); } return hostAddr; } final String customMappedHost = findHost(host); if (customMappedHost != null) { mHostCache.put(host, customMappedHost); if (Utils.isDebugBuild()) { Log.d(RESOLVER_LOGTAG, "Got mapped address " + customMappedHost + " for host " + host); } return customMappedHost; } initDns(); // Use TCP DNS Query if enabled. if (mDns != null && mPreferences.getBoolean(KEY_TCP_DNS_QUERY, false)) { final Name name = new Name(host); final Record query = Record.newRecord(name, Type.A, DClass.IN); if (query == null) return host; final Message response; try { response = mDns.send(Message.newQuery(query)); } catch (final IOException e) { return host; } if (response == null) return host; final Record[] records = response.getSectionArray(Section.ANSWER); if (records == null || records.length < 1) throw new IOException("Could not find " + host); String hostAddr = null; // Test each IP address resolved. for (final Record record : records) { if (record instanceof ARecord) { final InetAddress ipv4Addr = ((ARecord) record).getAddress(); if (ipv4Addr.isReachable(300)) { hostAddr = ipv4Addr.getHostAddress(); } } else if (record instanceof AAAARecord) { final InetAddress ipv6Addr = ((AAAARecord) record).getAddress(); if (ipv6Addr.isReachable(300)) { hostAddr = ipv6Addr.getHostAddress(); } } if (hostAddr != null) { mHostCache.put(host, hostAddr); if (Utils.isDebugBuild()) { Log.d(RESOLVER_LOGTAG, "Resolved address " + hostAddr + " for host " + host); } return hostAddr; } } // No address is reachable, but I believe the IP is correct. final Record record = records[0]; if (record instanceof ARecord) { final InetAddress ipv4Addr = ((ARecord) record).getAddress(); hostAddr = ipv4Addr.getHostAddress(); } else if (record instanceof AAAARecord) { final InetAddress ipv6Addr = ((AAAARecord) record).getAddress(); hostAddr = ipv6Addr.getHostAddress(); } else if (record instanceof CNAMERecord) return resolve(((CNAMERecord) record).getTarget().toString()); mHostCache.put(host, hostAddr); if (Utils.isDebugBuild()) { Log.d(RESOLVER_LOGTAG, "Resolved address " + hostAddr + " for host " + host); } return hostAddr; } if (Utils.isDebugBuild()) { Log.w(RESOLVER_LOGTAG, "Resolve address " + host + " failed, using original host"); } return host; } private String findHost(final String host) { for (final String rule : mHostMapping.getAll().keySet()) { if (hostMatches(host, rule)) return mHostMapping.getString(rule, null); } return null; } private void initDns() throws IOException { if (mDns != null) return; mDns = mLocalMappingOnly ? null : new SimpleResolver(mDnsAddress); if (mDns != null) { mDns.setTCP(true); } } private static boolean hostMatches(final String host, final String rule) { if (rule == null || host == null) return false; if (rule.startsWith(".")) return host.toLowerCase().endsWith(rule.toLowerCase()); return host.equalsIgnoreCase(rule); } private static boolean isValidIpAddress(final String address) { if (isEmpty(address)) return false; return InetAddressUtilsHC4.isIPv4Address(address) || InetAddressUtilsHC4.isIPv6Address(address); } private static class HostCache extends LinkedHashMap<String, String> { private static final long serialVersionUID = -9216545511009449147L; HostCache(final int initialCapacity) { super(initialCapacity); } @Override public String put(final String key, final String value) { if (value == null) return value; return super.put(key, value); } } }