diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 0a828750..b8af8231 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,8 +1,8 @@ + android:versionCode="1505000" + android:versionName="1.50.50.0"> diff --git a/app/src/main/assets/additionalHosts.txt b/app/src/main/assets/additionalHosts.txt index 78fba872..f3ceefda 100644 --- a/app/src/main/assets/additionalHosts.txt +++ b/app/src/main/assets/additionalHosts.txt @@ -1,22 +1,31 @@ # Add your own additional hosts here! -# Supports Blacklist, whitelist and map to custom IP +# Supports blacklist, whitelist and map to custom IP -# Format for Blacklist: 1 host name per line (sample below). +# WARNING! You must not add huge lists here! +# This is only for overruling the configured filter. + +# Format for blacklist: 1 host name per line (sample below). # Wildcard Character "*" can be used for host blocking. -# Host name and all sub domains will be blocked. +# Host name and all sub domains will be blocked, +# unless there is a more specific rule. sample4711.justasample.com sample4712.justasample.com -# In order to white list a specific host, add a -# "!" prefix in front of the host name. -# Whitelisting does NOT support wildcards "*" +# In order to white list a specific host, use "!" prefix. +# Wildcard Character "*" can be used for whitelisting. +# Host name and all sub domains will be whitelisted, +# unless there is a more specific rule. !whitelistsample.justasample.com -# In order to forward to custom ip add a ">" in front -# of the host name followed by whitespace and mapped IP +# PRIORITIES in case of conflicting entries +# 1. Explicit entries without wildcards +# 2. Wildcard entries prioritized along the sequence in this file +# 3. Downloaded blocklist ->router.home 192.168.2.1 +# In order to forward to custom ip add a ">" in front of the host, +# followed by whitespace and mapped IP. +>router.home 192.168.2.1 diff --git a/app/src/main/assets/dnsfilter.conf b/app/src/main/assets/dnsfilter.conf index 92b31244..5bec836b 100644 --- a/app/src/main/assets/dnsfilter.conf +++ b/app/src/main/assets/dnsfilter.conf @@ -1,3 +1,9 @@ +############################################# +# WARNING! FOR EXPERTS ONLY! # +# This is personalDNSfilter configuration! # +# Only edit this file if you are an expert! # +############################################# + # detectDNS = true|false. # if true, the DNS servers will be detected if possible. # if false, the DNS Servers will be taken from the fallbackDNS setting below. @@ -176,10 +182,6 @@ filterAutoUpdateURL_switchs = true; true; false; false; false; false; false; fal # DO NOT CHANGE! - will be set internally! previousAutoUpdateURL = -# additionalHosts_lastImportTS - the time stamp of additionalHosts.txt when it was last imported -# DO NOT CHANGE! - will be set internally! -additionalHosts_lastImportTS = 0 - # reloadIntervalDays - specifies the number of days, after the filter gets updated when configured. reloadIntervalDays =7 @@ -200,14 +202,3 @@ footerLink = Want to support us? Feel free to FAQ, or ask our Telegram group.\nA bad rating is not motivating to provide this free app further. - -# overrule filterHostFile with filter.=true|false (true will be blocked, false will not be blocked). - -#allow bild.de - remove # below to enable -#filter.cdn1.smartadserver.com = false -#filter.ec-ns.sascdn.com = false -#filter.acdn.adnxs.com=false - -#allow spiegel.de - remove # below to enable -#filter.imagesrv.adition.com=false -#filter.spiegel-de.spiegel.de=false diff --git a/app/src/main/java/dnsfilter/BlockedHosts.java b/app/src/main/java/dnsfilter/BlockedHosts.java index dec9a716..1eb9612e 100644 --- a/app/src/main/java/dnsfilter/BlockedHosts.java +++ b/app/src/main/java/dnsfilter/BlockedHosts.java @@ -67,23 +67,94 @@ public void objectToBytes(Object object, byte[] data, int offs) { private static Object NOT_NULL = new Object(); private LRUCache okCache; private LRUCache filterListCache; - private Hashtable hostsFilterOverRule; private int sharedLocks = 0; private boolean exclusiveLock = false; + private Hashtable hostsFilterOverRule = new Hashtable(); + private HugePackedSet blockedHostsHashes; - private Vector blockedPatterns; - public BlockedHosts(int maxCountEstimate, int okCacheSize, int filterListCacheSize, Hashtable hostsFilterOverRule) { + private class OverrulePattern { + private String[] pattern; + private boolean filter; + private int hashcode; + private OverrulePattern (String patternString, boolean filter){ + this.pattern = patternString.split("\\*", -1); + this.filter = filter; + hashcode = patternString.hashCode(); + if (!filter) + hashcode = ~hashcode; + } + + @Override + public int hashCode() { + return hashcode; + } + + @Override + public boolean equals(Object obj) { + if (obj != null && hashcode == obj.hashCode() && obj instanceof OverrulePattern) + return patternEqual((OverrulePattern) obj); + else + return false; + } + + private boolean patternEqual(OverrulePattern overrulePattern) { + if (pattern.length == overrulePattern.pattern.length) { + for (int i = 0; i < pattern.length; i++) { + if (!pattern[i].equals(overrulePattern.pattern[i])) + return false; + } + return true; + } else + return false; + } + + private boolean match(String host) { + + // Iterate over the parts. + for (int i = 0; i < pattern.length; i++) { + String part = pattern[i]; + + int idx = -1; + if (i < pattern.length-1) + idx = host.indexOf(part); + else + idx = host.lastIndexOf(part); + + if (i == 0 && !part.equals("") && idx != 0) { + // i == 0 ==> we are on the first fixed part + // first fixed part is not empty ==> Matching String must start with first fixed part + // if not, no match! + return false; + } + if (i == pattern.length-1 && !part.equals("") && idx + part.length() != host.length()) { + // i == last part + // last part is not empty ==> Matching String must end with last part + // if not, no match + return false; + } + // part not detected in the text. + if (idx == -1) { + return false; + } + // Move ahead, towards the right of the text. + host = host.substring(idx + part.length()); + } + return true; + } + + } + private Vector overrulePatterns = new Vector(); + + public BlockedHosts(int maxCountEstimate, int okCacheSize, int filterListCacheSize) { okCache = new LRUCache(okCacheSize); filterListCache = new LRUCache(filterListCacheSize); if (ExecutionEnvironment.getEnvironment().debug()) Logger.getLogger().logLine("CACHE SIZE:"+okCacheSize+", "+filterListCacheSize); - this.hostsFilterOverRule = hostsFilterOverRule; - int slots = maxCountEstimate / 6000; if ((slots % 2) == 0) slots++; @@ -91,22 +162,58 @@ public BlockedHosts(int maxCountEstimate, int okCacheSize, int filterListCacheSi blockedHostsHashes = new HugePackedSet(slots, PACK_MGR); } - private BlockedHosts(HugePackedSet blockedHostsHashes, Vector blockedPatterns, int okCacheSize, int filterListCacheSize, Hashtable hostsFilterOverRule) { + private BlockedHosts(HugePackedSet blockedHostsHashes, int okCacheSize, int filterListCacheSize) { this.blockedHostsHashes = blockedHostsHashes; - this.blockedPatterns = blockedPatterns; okCache = new LRUCache(okCacheSize); filterListCache = new LRUCache(filterListCacheSize); if (ExecutionEnvironment.getEnvironment().debug()) Logger.getLogger().logLine("CACHE SIZE:"+okCacheSize+", "+filterListCacheSize); + } + + + public void addOverrule(String host, boolean filter) { + host = host.toLowerCase(); + + if (host.indexOf("*") != -1) { + overrulePatterns.add(new OverrulePattern(host, filter)); + clearCache(!filter); + } + else { + hostsFilterOverRule.put(host, new Boolean(filter)); + clearCache(host, !filter); + } + } + + public void removeOverrule (String host, boolean filter) { + host = host.toLowerCase(); + + if (host.indexOf("*") != -1) { + overrulePatterns.remove(new OverrulePattern(host, filter)); + clearCache(filter); + } + else { + Boolean val = hostsFilterOverRule.get(host); + if (val != null && val.booleanValue() == filter) { + hostsFilterOverRule.remove(host); + clearCache(host, filter); + } + } + } - this.hostsFilterOverRule = hostsFilterOverRule; + private void clearCache(String host, boolean filter) { + long hostHash = Utils.getLongStringHash((String) host); + if (filter) + filterListCache.remove(hostHash); + else + okCache.remove(hostHash); } - public void setHostsFilterOverRule(Hashtable hostsFilterOverRule){ - if (hostsFilterOverRule == null) - throw new IllegalArgumentException("Argument null not allowed!"); - this.hostsFilterOverRule = hostsFilterOverRule; + private void clearCache(boolean filter) { + if (filter) + filterListCache.clear(); + else + okCache.clear(); } @@ -154,39 +261,15 @@ public static boolean checkIndexVersion(String path) throws IOException { return HugePackedSet.checkIndexVersion(path); } - public static BlockedHosts loadPersistedIndex(String path, boolean inMemory, int okCacheSize, int filterListCacheSize, Hashtable hostsFilterOverRule) throws IOException { - Vector blockedPatterns = null; - File patternFile = new File(path+"/blockedpatterns"); - if (patternFile.exists()){ - BufferedReader fin = new BufferedReader(new InputStreamReader(new FileInputStream(patternFile))); - blockedPatterns = new Vector(); - String entry; - while ( (entry = fin.readLine()) != null) { - blockedPatterns.addElement( ((String)entry).trim().split("\\*", -1)); - } - fin.close(); - } - return new BlockedHosts(HugePackedSet.load(path, inMemory, PACK_MGR), blockedPatterns, okCacheSize, filterListCacheSize, hostsFilterOverRule); + public static BlockedHosts loadPersistedIndex(String path, boolean inMemory, int okCacheSize, int filterListCacheSize) throws IOException { + return new BlockedHosts(HugePackedSet.load(path, inMemory, PACK_MGR), okCacheSize, filterListCacheSize); } + public void persist(String path) throws IOException { try { lock(1); blockedHostsHashes.persist(path); - if (blockedPatterns != null) { - OutputStream patterns = new BufferedOutputStream(new FileOutputStream(path + "/blockedpatterns")); - Iterator it = blockedPatterns.iterator(); - while (it.hasNext()) { - String[] fixedParts = (String[]) it.next(); - String patternStr = fixedParts[0]; - for (int i = 1; i < fixedParts.length; i++) { - patternStr = patternStr + "*" + fixedParts[i]; - } - patterns.write((patternStr + "\n").getBytes()); - } - patterns.flush(); - patterns.close(); - } } finally { unLock(1); } @@ -216,20 +299,6 @@ public void finalPrepare(int maxCountEstimate) { } - - private Vector getInitializedPatternStruct() { - if (blockedPatterns==null) - blockedPatterns = new Vector(); - return blockedPatterns; - } - - - public void clearCache(String host) { - long hostHash = Utils.getLongStringHash((String) host.toLowerCase()); - okCache.remove(hostHash); - filterListCache.remove(hostHash); - } - public boolean update(Object host) throws IOException { try { lock(1); @@ -247,64 +316,23 @@ public boolean update(Object host) throws IOException { } } - @Override public boolean add(Object host) { - if (((String) host).indexOf("*") == -1) - return blockedHostsHashes.add(Utils.getLongStringHash((String) ((String) host).toLowerCase())); - else { //Pattern - getInitializedPatternStruct().addElement( ((String)host).trim().toLowerCase().split("\\*", -1)); - return true; - } + return blockedHostsHashes.add(Utils.getLongStringHash((String) ((String) host).toLowerCase())); } - private boolean containsPatternMatch(String host) { - if ( blockedPatterns == null) - return false; + private OverrulePattern getOverrulePattern (String host) { - Iterator it = blockedPatterns.iterator(); + Iterator it = overrulePatterns.iterator(); while (it.hasNext()) { - String[] fixedParts = (String[]) it.next(); - if (wildCardMatch(fixedParts, host)) - return true; + OverrulePattern pattern = it.next(); + if (pattern.match(host)) + return pattern; } - return false; + return null; } - private static boolean wildCardMatch(String[] fixedParts, String host) { - - // Iterate over the parts. - for (int i = 0; i < fixedParts.length; i++) { - String part = fixedParts[i]; - - int idx = -1; - if (i < fixedParts.length-1) - idx = host.indexOf(part); - else - idx = host.lastIndexOf(part); - - if (i == 0 && !part.equals("") && idx != 0) { - // i == 0 ==> we are on the first fixed part - // first fixed part is not empty ==> Matching String must start with first fixed part - // if not, no match! - return false; - } - if (i == fixedParts.length-1 && !part.equals("") && idx + part.length() != host.length()) { - // i == last part - // last part is not empty ==> Matching String must end with last part - // if not, no match - return false; - } - // part not detected in the text. - if (idx == -1) { - return false; - } - // Move ahead, towards the right of the text. - host = host.substring(idx + part.length()); - } - return true; - } @Override public boolean contains(Object object) { @@ -342,15 +370,17 @@ private boolean contains(String hostName, long hosthash, boolean checkParent, bo while (idx != -1) { - if (hostsFilterOverRule != null) { - Object val = hostsFilterOverRule.get(hostName); - if (val != null) - return ((Boolean) val).booleanValue(); + Boolean filter = hostsFilterOverRule.get(hostName); + if (filter != null) + return filter.booleanValue(); + + if (checkPattern) { + OverrulePattern patternMatch = getOverrulePattern(hostName); + if (patternMatch != null) + return patternMatch.filter; } - if (blockedHostsHashes.contains(hosthash)) - return true; - if (checkPattern && containsPatternMatch(hostName)) + if (blockedHostsHashes.contains(hosthash)) return true; if (checkParent) { @@ -370,8 +400,7 @@ public void clear() { filterListCache.clear(); okCache.clear(); hostsFilterOverRule = null; // do not clear as provided from outside and reused - if (blockedPatterns != null) - blockedPatterns.clear(); + overrulePatterns = null; // do not clear as provided from outside and reused } protected void migrateTo(BlockedHosts hostFilter) { @@ -385,7 +414,7 @@ protected void migrateTo(BlockedHosts hostFilter) { hostsFilterOverRule = hostFilter.hostsFilterOverRule; - blockedPatterns = hostFilter.blockedPatterns; + overrulePatterns = hostFilter.overrulePatterns; //blockedHostsHashes.migrateTo(hostFilter.blockedHostsHashes); blockedHostsHashes = hostFilter.blockedHostsHashes; diff --git a/app/src/main/java/dnsfilter/ConfigurationAccess.java b/app/src/main/java/dnsfilter/ConfigurationAccess.java index 0d65eaa9..09d93ec0 100644 --- a/app/src/main/java/dnsfilter/ConfigurationAccess.java +++ b/app/src/main/java/dnsfilter/ConfigurationAccess.java @@ -46,6 +46,8 @@ public boolean isLocal() { abstract public void updateConfig(byte[] config) throws IOException; + public abstract void updateConfigMergeDefaults(byte[] config) throws IOException; + abstract public byte[] getAdditionalHosts(int limit) throws IOException; abstract public void updateAdditionalHosts(byte[] bytes) throws IOException; diff --git a/app/src/main/java/dnsfilter/DNSFilterManager.java b/app/src/main/java/dnsfilter/DNSFilterManager.java index 756eae90..44d6915a 100644 --- a/app/src/main/java/dnsfilter/DNSFilterManager.java +++ b/app/src/main/java/dnsfilter/DNSFilterManager.java @@ -64,7 +64,7 @@ of the License, or (at your option) any later version. public class DNSFilterManager extends ConfigurationAccess { - public static final String VERSION = "1504901"; + public static final String VERSION = "1505000"; private static DNSFilterManager INSTANCE = new DNSFilterManager(); @@ -77,7 +77,6 @@ public class DNSFilterManager extends ConfigurationAccess { private static int okCacheSize = 500; private static int filterListCacheSize = 500; private static boolean reloadUrlChanged; - private static String additionalHostsImportTS = "0"; private static boolean validIndex; private static boolean aborted = false; @@ -85,7 +84,6 @@ public class DNSFilterManager extends ConfigurationAccess { private static LoggerInterface TRAFFIC_LOG; private static BlockedHosts hostFilter = null; - private static Hashtable hostsFilterOverRule = null; private static Hashtable customIPMappings = null; private boolean serverStopped = true; private boolean reloading_filter = false; @@ -357,7 +355,7 @@ private byte[] mergeAndPersistConfig(byte[] currentConfigBytes) throws IOExcepti } mergedout.flush(); mergedout.close(); - Logger.getLogger().logLine("Merged configuration 'dnsfilter.conf' after update to version " + DNSFilterManager.VERSION + "!"); + Logger.getLogger().logLine("Merged configuration 'dnsfilter.conf' with defaults of current version " + DNSFilterManager.VERSION + "!"); InputStream in = new FileInputStream(mergedConfig); byte[] configBytes = Utils.readFully(in, 1024); in.close(); @@ -413,6 +411,18 @@ public void updateConfig(byte[] config) throws IOException { } } + @Override + public void updateConfigMergeDefaults(byte[] config) throws IOException { + try { + config = mergeAndPersistConfig(config); + this.config.load(new ByteArrayInputStream(config)); + Logger.getLogger().message("Config changed!\nRestart might be required!"); + } catch (IOException e) { + throw new ConfigurationAccessException(e.getMessage(), e); + } + } + + @Override public byte[] getAdditionalHosts(int limit) throws IOException { try { @@ -686,9 +696,11 @@ else if (contentencoding == null || "identity".equals(contentencoding)) } } + + // invalidate index in order to force rebuild + setIndexOutdated(true); // update last loaded URL after successfull update - // setting additionalhosts TS = 0 is a hack here - but it enforces that index is recreated - updateIndexReloadInfoConfFile(filterReloadURL, "0"); + updateIndexReloadInfoConfFile(filterReloadURL); reloadUrlChanged = false; Logger.getLogger().logLine("Updating filter completed!"); @@ -713,6 +725,19 @@ else if (contentencoding == null || "identity".equals(contentencoding)) return true; //completed } + private void setIndexOutdated(boolean outdated) throws IOException{ + File f =new File(getPath() + "IDX_OUTDATED"); + if (outdated) { + f.createNewFile(); + } + else + if (!f.delete()) throw new IOException("Cannot delete 'IDX_OUTDATED' file!"); + } + + private boolean isIndexOutdated(){ + return new File(getPath() + "IDX_OUTDATED").exists(); + } + public int[] readHostFileEntry(InputStream in, byte[] buf) throws IOException { int token = 0; @@ -824,11 +849,8 @@ private void rebuildIndex() throws IOException { Logger.getLogger().logLine("Reading filter file and building index...!"); File filterfile = new File(getPath() + filterhostfile); - File additionalHosts = new File(getPath() + "additionalHosts.txt"); File indexFile = new File(getPath() + filterhostfile + ".idx"); BufferedReader fin = new BufferedReader(new InputStreamReader(new FileInputStream(filterfile))); - BufferedReader addHostIn = new BufferedReader(new InputStreamReader(new FileInputStream(additionalHosts))); - int size = 0; @@ -863,42 +885,36 @@ private void rebuildIndex() throws IOException { int estimatedIdxCount = ffileCount; if (estimatedIdxCount == -1) //estimate based on file size - estimatedIdxCount = Math.max(1, (int) ((filterfile.length() + additionalHosts.length()) / 30)); + estimatedIdxCount = Math.max(1, (int) ((filterfile.length() ) / 30)); else //known ff entry count plus the estimated entries from add hosts. - estimatedIdxCount= estimatedIdxCount + ((int) (additionalHosts.length() / 20)); + estimatedIdxCount= estimatedIdxCount; - BlockedHosts hostFilterSet = new BlockedHosts(estimatedIdxCount, okCacheSize, filterListCacheSize, hostsFilterOverRule); + BlockedHosts hostFilterSet = new BlockedHosts(estimatedIdxCount, okCacheSize, filterListCacheSize); String entry = firstffLine; // first line from filterfile as read above boolean skipFFprep = false; if (ffDownloaded && ffileCount != -1) { - // Filterfile known ... We can skip preparation and directly go to additional hosts entry = null; size = ffileCount; skipFFprep = true; } - while (!aborted && (entry != null || fin != addHostIn)) { - if (entry == null) { - //ready with filter file continue with additionalHosts - fin.close(); - fin = addHostIn; - } else { - String[] hostEntry = parseHosts(entry); - if (hostEntry != null && !hostEntry[1].equals("localhost")) { - hostFilterSet.prepareInsert(hostEntry[1]); - size++; - } + while (!aborted && entry != null) { + + String[] hostEntry = parseHosts(entry); + if (hostEntry != null && !hostEntry[1].equals("localhost")) { + hostFilterSet.prepareInsert(hostEntry[1]); + size++; } + entry = fin.readLine(); } fin.close(); - if (fin != addHostIn) - addHostIn.close(); + if (aborted) { Logger.getLogger().logLine("Aborting indexing!"); Logger.getLogger().message("Indexing aborted!"); @@ -913,7 +929,7 @@ private void rebuildIndex() throws IOException { Logger.getLogger().logLine("Building index for " + size + " entries...!"); fin = new BufferedReader(new InputStreamReader(new FileInputStream(filterfile))); - addHostIn = new BufferedReader(new InputStreamReader(new FileInputStream(additionalHosts))); + File uniqueEntriyFile = new File(getPath() + "uniqueentries.tmp"); BufferedOutputStream fout = null; @@ -928,39 +944,30 @@ private void rebuildIndex() throws IOException { if (ffDownloaded) fin.readLine(); // skip first comment line - while (!aborted && ((entry = fin.readLine()) != null || fin != addHostIn)) { - if (entry == null) { - //ready with filter file continue with additionalHosts - fin.close(); - fin = addHostIn; - ffileCount=uniqueEntries; - } else { - String[] hostEntry; - if (!ffDownloaded || fin == addHostIn ) - hostEntry = parseHosts(entry); - else // reading downloaded filterfile with known plain hosts format - hostEntry = new String[] {"",entry}; - - if (hostEntry != null && !hostEntry[1].equals("localhost")) { - if (!hostFilterSet.add(hostEntry[1])) - ;//Logger.getLogger().logLine("Duplicate detected ==>" + entry); - else { - uniqueEntries++; - if (fin != addHostIn && filterHostsFileRemoveDuplicates) - fout.write((hostEntry[1] + "\n").getBytes()); // create filterhosts without duplicates - } - processed++; - if (processed % 10000 == 0) { - Logger.getLogger().message("Building index for " + processed + "/" + size + " entries completed!"); - } + while (!aborted && (entry = fin.readLine()) != null ) { + + String[] hostEntry; + if (!ffDownloaded) + hostEntry = parseHosts(entry); + else // reading downloaded filterfile with known plain hosts format + hostEntry = new String[] {"",entry}; + if (hostEntry != null && !hostEntry[1].equals("localhost")) { + if (!hostFilterSet.add(hostEntry[1])) + ;//Logger.getLogger().logLine("Duplicate detected ==>" + entry); + else { + uniqueEntries++; + if (filterHostsFileRemoveDuplicates) + fout.write((hostEntry[1] + "\n").getBytes()); // create filterhosts without duplicates + } + processed++; + if (processed % 10000 == 0) { + Logger.getLogger().message("Building index for " + processed + "/" + size + " entries completed!"); } } } + ffileCount = uniqueEntries; Logger.getLogger().message("Building index for " + processed + "/" + size + " entries completed!"); fin.close(); - if (fin != addHostIn) - addHostIn.close(); - if (aborted) { Logger.getLogger().logLine("Indexing aborted!"); if (filterHostsFileRemoveDuplicates) @@ -991,7 +998,7 @@ private void rebuildIndex() throws IOException { hostFilterSet.persist(getPath() + filterhostfile + ".idx"); hostFilterSet.clear(); //release memory - hostFilterSet = BlockedHosts.loadPersistedIndex(indexFile.getAbsolutePath(), false, okCacheSize, filterListCacheSize, hostsFilterOverRule); //loads only file handles not the whole structure. + hostFilterSet = BlockedHosts.loadPersistedIndex(indexFile.getAbsolutePath(), false, okCacheSize, filterListCacheSize); //loads only file handles not the whole structure. if (hostFilter != null) { hostFilter.migrateTo(hostFilterSet); @@ -1000,14 +1007,14 @@ private void rebuildIndex() throws IOException { hostFilter = hostFilterSet; DNSResponsePatcher.init(hostFilter, TRAFFIC_LOG); //give newly created filter to DNSResponsePatcher } + applyOverrules(); } finally { if (lock) hostFilter.unLock(1); //Update done! Release exclusive lock so readers are welcome! } + setIndexOutdated(false); validIndex = true; Logger.getLogger().logLine("Processing new filter file completed!"); - additionalHostsImportTS = "" + additionalHosts.lastModified(); - updateIndexReloadInfoConfFile(filterReloadURL, additionalHostsImportTS); //update last loaded URL and additionalHosts } finally { updatingFilter = false; INSTANCE.notifyAll(); @@ -1015,18 +1022,57 @@ private void rebuildIndex() throws IOException { } } + private void applyOverrules() throws IOException { + File additionalHosts = new File(getPath() + "additionalHosts.txt"); + BufferedReader addHostIn = new BufferedReader(new InputStreamReader(new FileInputStream(additionalHosts))); + customIPMappings.clear(); + + String entry = null; + while ((entry = addHostIn.readLine()) != null) { + entry = entry.trim().toLowerCase(); + if (!entry.equals("") && !entry.startsWith("#")) { + if (entry.startsWith(">")) + applyCustomIpMapping(entry.substring(1).trim()); + if (entry.startsWith("!")) + hostFilter.addOverrule(entry.substring(1).trim(), false); + else + hostFilter.addOverrule(entry, true); + } + } + addHostIn.close(); + } + + private void applyCustomIpMapping(String entry) { + StringTokenizer tokens = new StringTokenizer(entry); + try { + String host = tokens.nextToken().trim().toLowerCase(); + String ip = tokens.nextToken().trim(); + + InetAddress address = InetAddress.getByName(ip); + byte[] addressBytes = address.getAddress(); + if (addressBytes.length == 4) + customIPMappings.put(">4"+host, addressBytes); + else + customIPMappings.put(">6"+host, addressBytes); + + } catch (Exception e) { + Logger.getLogger().logLine("Cannot apply custom mapping "+entry); + Logger.getLogger().logLine(e.toString()); + } + } + private void reloadFilter(boolean async) throws IOException { try { ExecutionEnvironment.getEnvironment().wakeLock(); //ensure device stays awake until filter reload is completed - File filterfile = new File(getPath() + filterhostfile); File downloadInfoFile = new File(getPath() + filterhostfile + ".DLD_CNT"); File additionalHosts = new File(getPath() + "additionalHosts.txt"); + + validIndex = !isIndexOutdated(); + if (!additionalHosts.exists()) additionalHosts.createNewFile(); - boolean needRedloadAdditionalHosts = !("" + additionalHosts.lastModified()).equals(additionalHostsImportTS); - if (filterfile.exists() && downloadInfoFile.exists() && !reloadUrlChanged) { nextReload = filterReloadIntervalDays * 24 * 60 * 60 * 1000 + downloadInfoFile.lastModified(); } else @@ -1034,11 +1080,8 @@ private void reloadFilter(boolean async) throws IOException { File indexFile = new File(getPath() + filterhostfile + ".idx"); if (indexFile.exists() && validIndex && BlockedHosts.checkIndexVersion(indexFile.getAbsolutePath())) { - hostFilter = BlockedHosts.loadPersistedIndex(indexFile.getAbsolutePath(), false, okCacheSize, filterListCacheSize, hostsFilterOverRule); - if (needRedloadAdditionalHosts && filterfile.exists() && nextReload != 0) { - // additionalHosts where modified - reload async and keep current index until reload completed in order to prevent start without any filter! - new Thread(new AsyncIndexBuilder()).start(); - } + hostFilter = BlockedHosts.loadPersistedIndex(indexFile.getAbsolutePath(), false, okCacheSize, filterListCacheSize); + applyOverrules(); } else if (filterfile.exists() && nextReload != 0) { if (!async) { rebuildIndex(); @@ -1051,27 +1094,21 @@ private void reloadFilter(boolean async) throws IOException { } } - private void updateIndexReloadInfoConfFile(String url, String additionalHosts_lastImportTS) { + private void updateIndexReloadInfoConfFile(String url) { try { ByteArrayOutputStream out = new ByteArrayOutputStream(); BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(getPath() + "dnsfilter.conf"))); String ln; - boolean found1 = false; - boolean found2 = false; + boolean found = false; while ((ln = reader.readLine()) != null) { if (url != null && ln.startsWith("previousAutoUpdateURL")) { - found1 = true; + found = true; ln = "previousAutoUpdateURL = " + url; - } else if (additionalHosts_lastImportTS != null && ln.startsWith("additionalHosts_lastImportTS")) { - found2 = true; - ln = "additionalHosts_lastImportTS = " + additionalHosts_lastImportTS; } out.write((ln + "\r\n").getBytes()); } - if (!found1 && url != null) + if (!found ) out.write(("previousAutoUpdateURL = " + url + "\r\n").getBytes()); - if (!found2 && additionalHosts_lastImportTS != null) - out.write(("additionalHosts_lastImportTS = " + additionalHosts_lastImportTS + "\r\n").getBytes()); out.flush(); reader.close(); @@ -1111,9 +1148,7 @@ public void updateFilter(String entries, boolean filter) throws IOException { // find which entries need to be overwritten while (entryTokens.hasMoreTokens()) { String entry = entryTokens.nextToken().trim(); - boolean filterContains = hostFilter.contains(entry); - if ((filter && !filterContains) || (!filter && filterContains)) - entriestoChange.add(entry); + entriestoChange.add(entry); } // update additional hosts file @@ -1128,7 +1163,7 @@ public void updateFilter(String entries, boolean filter) throws IOException { boolean copyPasteSection = false; boolean listSection = false; while ((entry = addHostIn.readLine()) != null) { - String host = entry; + String host = entry.toLowerCase(); boolean hostEntry = !(entry.trim().equals("") && !entry.startsWith("#") && !entry.startsWith(">")); if (entry.startsWith("!")) host = entry.trim().substring(1); @@ -1166,10 +1201,6 @@ public void updateFilter(String entries, boolean filter) throws IOException { additionalHostsNew.renameTo(additionalHosts); - //index was updated ==> update timestamp in order to avoid reindexing - additionalHostsImportTS = "" + additionalHosts.lastModified(); - updateIndexReloadInfoConfFile(filterReloadURL, additionalHostsImportTS); //update last loaded URL and additionalHosts - Logger.getLogger().message("Updated " + entriestoChange.size() + " host(s)!"); if (indexingAborted) { @@ -1220,33 +1251,13 @@ private void writeNewEntries(boolean filter, HashSet entriestoChange, Bu if (!filter) excludePref="!"; - if (hostsFilterOverRule == null) { - hostsFilterOverRule = new Hashtable(); - hostFilter.setHostsFilterOverRule(hostsFilterOverRule); - } Iterator entryit = entriestoChange.iterator(); while (entryit.hasNext()) { String entry = entryit.next(); - boolean skip = false; - - hostsFilterOverRule.remove(entry); - hostFilter.clearCache(entry); - - // skip previously whitelisted entry which shall be blacklisted again and is already part of the default filters - // Requires that the index is up-to-date (lastFinishedIndexRunTS > lastRequestedIndexRunTS) - skip = (filter && hostFilter.contains(entry)); - - if (!skip) { - addHostOut.write( "\n"+excludePref + entry); - if (!filter) { - hostsFilterOverRule.put(entry, false); //whitelisting via hostfilter overrule - } - else - hostFilter.update(entry); //update index directly - } - if (filter) - hostFilter.updatePersist(); + hostFilter.removeOverrule(entry.toLowerCase(), !filter); + addHostOut.write( "\n"+excludePref + entry); + hostFilter.addOverrule(entry.toLowerCase(), false); } } @@ -1261,16 +1272,12 @@ private void initEnv() { filterHostsFileRemoveDuplicates = false; validIndex = true; hostFilter = null; - if (hostsFilterOverRule != null) { - hostsFilterOverRule.clear(); - hostsFilterOverRule = null; - } + if (customIPMappings != null) { customIPMappings.clear(); customIPMappings = null; DNSResolver.initLocalResolver(null, false, 0); } - additionalHostsImportTS = "0"; reloading_filter = false; } @@ -1321,7 +1328,6 @@ public void init() throws IOException { //start remote Control server if configured and not started already if (remoteAccessManager == null) { try { - int port = Integer.parseInt(config.getProperty("server_remote_ctrl_port", "-1")); String keyphrase = config.getProperty("server_remote_ctrl_keyphrase", ""); if (port != -1) @@ -1343,8 +1349,6 @@ public void init() throws IOException { if (config.getProperty("androidKeepAwake", "true").equalsIgnoreCase("true")) ExecutionEnvironment.getEnvironment().wakeLock(); - additionalHostsImportTS = config.getProperty("additionalHosts_lastImportTS", "0"); - //Init traffic Logger try { @@ -1374,61 +1378,20 @@ public void init() throws IOException { if (filterhostfile != null && filterActive) { - // load filter overrule values - + //Warn in case filter overruling within dnsfilter.conf is still used! Iterator entries = config.entrySet().iterator(); - while (entries.hasNext()) { Entry entry = (Entry) entries.next(); String key = (String) entry.getKey(); if (key.startsWith("filter.")) { - if (hostsFilterOverRule == null) - hostsFilterOverRule = new Hashtable(); - hostsFilterOverRule.put(key.substring(7), new Boolean(Boolean.parseBoolean(((String) entry.getValue()).trim()))); + Logger.getLogger().logLine("WARNING! '"+key+"' not supported anymore! Use additionalHosts.txt!"); } } - //load whitelisted hosts and custom mappings from additionalHosts.txt - boolean enableLocalResolver = Boolean.parseBoolean(config.getProperty("enableLocalResolver", "false")); int localTTL = Integer.parseInt(config.getProperty("localResolverTTL", "60")); - DNSResolver.initLocalResolver(null, enableLocalResolver, localTTL); - - File additionalHosts = new File(getPath() + "additionalHosts.txt"); - if (additionalHosts.exists()) { - BufferedReader addHostIn = new BufferedReader(new InputStreamReader(new FileInputStream(additionalHosts))); - String entry = null; - while ((entry = addHostIn.readLine()) != null) { - if (entry.startsWith("!")) { - if (hostsFilterOverRule == null) - hostsFilterOverRule = new Hashtable(); - hostsFilterOverRule.put(entry.substring(1).trim(), new Boolean(false)); - } else if (entry.startsWith(">")) { - if (customIPMappings == null) { - customIPMappings = new Hashtable(); - DNSResolver.initLocalResolver(customIPMappings, enableLocalResolver, localTTL); - } - StringTokenizer tokens = new StringTokenizer(entry.substring(1).trim()); - try { - String host = tokens.nextToken().trim().toLowerCase(); - String ip = tokens.nextToken().trim(); - - InetAddress address = InetAddress.getByName(ip); - byte[] addressBytes = address.getAddress(); - if (addressBytes.length == 4) - customIPMappings.put(">4"+host, addressBytes); - else - customIPMappings.put(">6"+host, addressBytes); - - } catch (Exception e) { - Logger.getLogger().logLine("Cannot apply custom mapping "+entry); - Logger.getLogger().logLine(e.toString()); - } - - } - } - addHostIn.close(); - } + customIPMappings = new Hashtable(); + DNSResolver.initLocalResolver(customIPMappings, enableLocalResolver, localTTL); // trigger regular filter update when configured filterReloadURL = getFilterReloadURL(config); @@ -1442,7 +1405,6 @@ public void init() throws IOException { reloadFilter(true); if (filterReloadURL != null) { - autoFilterUpdater = new AutoFilterUpdater(); Thread t = new Thread(autoFilterUpdater); t.setDaemon(true); diff --git a/app/src/main/java/dnsfilter/DNSServer.java b/app/src/main/java/dnsfilter/DNSServer.java index ff406dce..ea774c4c 100644 --- a/app/src/main/java/dnsfilter/DNSServer.java +++ b/app/src/main/java/dnsfilter/DNSServer.java @@ -48,7 +48,7 @@ public class DNSServer { protected InetSocketAddress address; protected int timeout; protected long lastPerformance = -1; - private static int bufSize=1024; + protected static int bufSize=1024; protected static int maxBufSize= -1; //will be read in static initializer below public static final int UDP = 0; //Via UDP public static final int TCP = 1; //Via TCP @@ -321,6 +321,10 @@ public static void invalidateAllUDPSessions() { @Override public void resolve(DatagramPacket request, DatagramPacket response) throws IOException { + boolean tcpFallback = false; + //need to ensure response as own data buffer in order to not overwrite the request, which might still be needed in case of TCP fallback + response.setData(new byte[bufSize],response.getOffset(),bufSize-response.getOffset()); + DatagramSocket socket = new DatagramSocket(); synchronized (sessions) { sessions.add(socket); @@ -339,8 +343,17 @@ public void resolve(DatagramPacket request, DatagramPacket response) throws IOEx } try { socket.receive(response); + if (isTruncatedResponse(response)) { + tcpFallback = true; + doTcpFallback(request, response); + } + checkResizeNeed(response); return; } catch (IOException eio) { + + if (tcpFallback) + throw eio; + synchronized (sessions) { if (!sessions.contains(socket)) throw new IOException("Sessions are closed due to network change!"); @@ -357,6 +370,33 @@ public void resolve(DatagramPacket request, DatagramPacket response) throws IOEx socket.close(); } } + + private void checkResizeNeed(DatagramPacket response) { + if (response.getOffset()+response.getLength() >= bufSize) { + synchronized (DNSServer.class) { + //Write access to static data, synchronization against the class is needed. + //Could be optimized in future by setting the buf size per DNSServer Instance and not static. + //However at the time the buffer is created, it is not known what will be the DNSServer used, because it be might be switched. + + if (response.getOffset()+response.getLength() >= bufSize && bufSize < maxBufSize) { //resize for future requests + bufSize = (int) Math.min(bufSize*1.2, maxBufSize); + Logger.getLogger().logLine("BUFFER RESIZE:"+bufSize); + } else if (bufSize >= maxBufSize ) + Logger.getLogger().logLine("MAX BUFFER SIZE reached:"+bufSize); + + response.setData(new byte[bufSize],response.getOffset(),bufSize-response.getOffset()); + } + } + } + + private void doTcpFallback(DatagramPacket request, DatagramPacket response) throws IOException { + Logger.getLogger().logLine("Truncated UDP response - fallback to TCP!"); + new TCP(address.getAddress(), address.getPort(), timeout, false, null).resolve(request, response); + } + + private boolean isTruncatedResponse(DatagramPacket response) { + return ((response.getData()[response.getOffset()+2] & 0XFF) & 2) == 2; + } } class TCP extends DNSServer { diff --git a/app/src/main/java/dnsfilter/SimpleDNSMessage.java b/app/src/main/java/dnsfilter/SimpleDNSMessage.java index 07cdb31f..422c7619 100644 --- a/app/src/main/java/dnsfilter/SimpleDNSMessage.java +++ b/app/src/main/java/dnsfilter/SimpleDNSMessage.java @@ -18,9 +18,14 @@ public class SimpleDNSMessage { public SimpleDNSMessage(byte[] data, int offs, int length) throws IOException { + this.data = data; this.offs = offs; this.length = length; + + if (length < 12) + return; //not a valid message, incomplete header + rqFlgs = data[offs+2]&0xFF; resFlgs = data[offs+3]&0xFF; @@ -34,7 +39,7 @@ public SimpleDNSMessage(byte[] data, int offs, int length) throws IOException { } public boolean isStandardQuery() { - return ( (rqFlgs >> 3) == 0); + return ( length >= 12 && (rqFlgs >> 3) == 0); } public Object[] getQueryData() { diff --git a/app/src/main/java/dnsfilter/android/AndroidEnvironment.java b/app/src/main/java/dnsfilter/android/AndroidEnvironment.java index 0182a2f1..ee66b740 100644 --- a/app/src/main/java/dnsfilter/android/AndroidEnvironment.java +++ b/app/src/main/java/dnsfilter/android/AndroidEnvironment.java @@ -118,7 +118,7 @@ public void wakeLock(){ PowerManager.WakeLock wakeLock = ((PowerManager) ctx.getSystemService(Context.POWER_SERVICE)).newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "personalDNSfilter:wakelock"); wakeLock.acquire(); wakeLooks.push(new Object[]{wifiLock, wakeLock}); - Logger.getLogger().logLine("Aquired WIFI lock and partial wake lock!"); + Logger.getLogger().logLine("Acquired WIFI lock and partial wake lock!"); } @Override diff --git a/app/src/main/java/dnsfilter/android/AppSelectorView.java b/app/src/main/java/dnsfilter/android/AppSelectorView.java index 706f4687..5b32d3bb 100644 --- a/app/src/main/java/dnsfilter/android/AppSelectorView.java +++ b/app/src/main/java/dnsfilter/android/AppSelectorView.java @@ -17,14 +17,16 @@ import android.graphics.Canvas; import android.widget.TextView; +import util.Logger; + public class AppSelectorView extends LinearLayout { private PackageManager pm = this.getContext().getPackageManager(); private boolean loaded = false; private String selectedApps = ""; - private int iconSize = 0; - private int corIconSize = 0; + private static int iconSizePx = 0; + private static float iconSizeDP = 96; private AsyncLoader runningUpdate = null; private ComparableAppInfoWrapper[] wrappers = null; @@ -75,6 +77,11 @@ private class AsyncLoader implements Runnable { @Override public synchronized void run() { + if (iconSizePx == 0) { + float scale = getResources().getDisplayMetrics().density; + iconSizePx = (int) (iconSizeDP * scale + 0.5f); + } + if (abort) return; @@ -92,18 +99,6 @@ public void run() { } }); - if (iconSize == 0) { - try { - Drawable icon = pm.getApplicationIcon("dnsfilter.android"); - iconSize = icon.getIntrinsicWidth(); - icon = resizeDrawable(icon, iconSize); - float factor = (float) iconSize / (float) icon.getIntrinsicWidth(); - corIconSize = (int) (((float) iconSize) * factor); - } catch (PackageManager.NameNotFoundException e) { - e.printStackTrace(); - } - } - String selectedapppackages = ("," + selectedApps + ",").replace(" ", ""); ApplicationInfo[] packages = pm.getInstalledApplications(PackageManager.GET_META_DATA).toArray(new ApplicationInfo[0]); @@ -128,8 +123,7 @@ public void run() { for (int i = 0; (i < wrappers.length && !abort); i++) { Drawable icon = (wrappers[i].wrapped.loadIcon(pm)); - if (icon.getIntrinsicWidth() != iconSize) - icon = resizeDrawable(icon, corIconSize); + icon = resizeDrawable(icon, iconSizePx); post(new UIUpdate(wrappers[i].checkBox, icon, this)); } loaded = !abort; diff --git a/app/src/main/java/dnsfilter/android/DNSProxyActivity.java b/app/src/main/java/dnsfilter/android/DNSProxyActivity.java index e8bbd162..d9b540ba 100644 --- a/app/src/main/java/dnsfilter/android/DNSProxyActivity.java +++ b/app/src/main/java/dnsfilter/android/DNSProxyActivity.java @@ -42,6 +42,7 @@ of the License, or (at your option) any later version. import android.text.Spanned; import android.text.TextWatcher; import android.text.method.LinkMovementMethod; +import android.transition.TransitionManager; import android.util.TypedValue; import android.view.ActionMode; import android.view.KeyEvent; @@ -50,6 +51,7 @@ of the License, or (at your option) any later version. import android.view.MotionEvent; import android.view.View; import android.view.View.OnClickListener; +import android.view.ViewGroup; import android.view.Window; import android.view.WindowManager; import android.widget.Button; @@ -124,9 +126,11 @@ public class DNSProxyActivity extends Activity implements OnClickListener, Logge protected static CheckBox enableAutoStartCheck; protected static CheckBox rootModeCheck; protected static CheckBox enableCloakProtectCheck; + protected static CheckBox manuallyEditConfChk; protected static EditText filterReloadIntervalView; protected static FilterConfig filterCfg; protected static EditText additionalHostsField; + protected static EditText manuallyEditField; protected static TextView scrollLockField; protected static Dialog advDNSConfigDia; protected static CheckBox manualDNSCheck; @@ -145,6 +149,7 @@ public class DNSProxyActivity extends Activity implements OnClickListener, Logge protected static boolean additionalHostsChanged = false; + protected static boolean manuallyConfEdited = false; protected static SuppressRepeatingsLogger myLogger; protected ScrollView scrollView = null; @@ -176,6 +181,8 @@ public class DNSProxyActivity extends Activity implements OnClickListener, Logge protected static boolean NO_VPN = false; + protected static boolean MSG_ACTIVE = false; + protected static int DISPLAY_WIDTH = 0; protected static DNSProxyActivity INSTANCE; @@ -207,6 +214,8 @@ public void timeoutNotification() { activity.setMessage(fromHtml(link_field_txt), link_field_color); else activity.setMessage(fromHtml(""+ CONFIG +""), link_field_color); + + MSG_ACTIVE = false; } @Override @@ -538,6 +547,11 @@ public void onCreate(Bundle savedInstanceState) { editFilterLoadCheck.setChecked(checked); editFilterLoadCheck.setOnClickListener(this); + checked = manuallyEditConfChk != null && manuallyEditConfChk.isChecked(); + manuallyEditConfChk= (CheckBox) findViewById(R.id.manuallyEditConfChk); + manuallyEditConfChk.setChecked(checked); + manuallyEditConfChk.setOnClickListener(this); + checked = editAdditionalHostsCheck != null && editAdditionalHostsCheck.isChecked(); editAdditionalHostsCheck = (CheckBox) findViewById(R.id.editAdditionalHosts); editAdditionalHostsCheck.setChecked(checked); @@ -559,6 +573,13 @@ public void onCreate(Bundle savedInstanceState) { additionalHostsField.setText(uiText); additionalHostsField.addTextChangedListener(this); + if (manuallyEditField != null) + uiText = manuallyEditField.getText().toString(); + + manuallyEditField = (EditText) findViewById(R.id.manuallyEditField); + manuallyEditField.setText(uiText); + manuallyEditField.addTextChangedListener(this); + findViewById(R.id.copyfromlog).setVisibility(View.GONE); handleAdvancedConfig(null); @@ -804,6 +825,16 @@ protected void loadAdditionalHosts() { } } + protected void loadManuallyEditConf() { + try { + byte[] content = CONFIG.readConfig(); + manuallyEditField.setText(new String(content)); + manuallyConfEdited = false; + } catch (IOException eio) { + Logger.getLogger().logLine("Can not load /PersonalDNSFilter/dnsfilter.conf!\n" + eio.toString()); + } + } + protected boolean persistAdditionalHosts() { String addHostsTxt = additionalHostsField.getText().toString(); @@ -818,6 +849,21 @@ protected boolean persistAdditionalHosts() { return additionalHostsChanged; } + protected boolean persistManuallyEditConf() { + String addHostsTxt = manuallyEditField.getText().toString(); + if (!addHostsTxt.equals("")) { + if (manuallyConfEdited) + try { + CONFIG.updateConfigMergeDefaults(addHostsTxt.getBytes()); + loadAndApplyConfig(false); + } catch (IOException eio) { + Logger.getLogger().logLine("Cannot persist manually edited config!\n" + eio.toString()); + } + } + return manuallyConfEdited; + } + + protected void handlefilterReload() { @@ -931,7 +977,7 @@ protected void restoreDefaultDNSConfig() { } } - protected void loadAndApplyConfig(boolean startApp) { + protected void loadAndApplyConfig(final boolean startApp) { config = getConfig(); @@ -946,8 +992,10 @@ protected void loadAndApplyConfig(boolean startApp) { public void run() { //Link field - link_field_txt = config.getProperty("footerLink",""); - link_field.setText(fromHtml(link_field_txt)); + if (!MSG_ACTIVE) { + link_field_txt = config.getProperty("footerLink", ""); + link_field.setText(fromHtml(link_field_txt)); + } //Log formatting filterLogFormat = config.getProperty("filterLogFormat", "($CONTENT)"); @@ -1123,6 +1171,9 @@ private String getFallbackDNSSettingFromUI(){ private void persistConfig() { try { + if (persistManuallyEditConf()) + return; + boolean changed = persistAdditionalHosts(); if (filterReloadIntervalView.getText().toString().equals("")) @@ -1244,6 +1295,7 @@ public void onClick(View destination) { openBrowser("https://www.zenz-home.com/personaldnsfilter/help/help.php"); return; } else if (destination == dnsField) { + persistConfig(); handleDNSConfigDialog(); return; } else if (destination == scrollLockField) { @@ -1303,7 +1355,7 @@ public void onClick(View destination) { if (destination == reloadFilterBtn) handlefilterReload(); - if (destination == advancedConfigCheck || destination == editAdditionalHostsCheck || destination == editFilterLoadCheck || destination == appWhiteListCheck || destination == backupRestoreCheck) { + if (destination == advancedConfigCheck || destination == editAdditionalHostsCheck || destination == manuallyEditConfChk || destination == editFilterLoadCheck || destination == appWhiteListCheck || destination == backupRestoreCheck) { handleAdvancedConfig((CheckBox)destination); } @@ -1550,6 +1602,8 @@ private void setVisibilityForAdvCfg(int v){ private void handleAdvancedConfig(CheckBox dest) { + prepareTransition((ViewGroup) findViewById(R.id.linearLayout4)); + ((TextView) findViewById(R.id.backupLog)).setText(""); if (advancedConfigCheck.isChecked()) { setVisibilityForAdvCfg(View.GONE); @@ -1572,12 +1626,15 @@ private void handleAdvancedConfig(CheckBox dest) { rootModeCheck.setVisibility(View.VISIBLE); enableCloakProtectCheck.setVisibility(View.VISIBLE); editAdditionalHostsCheck.setVisibility(View.VISIBLE); + manuallyEditConfChk.setVisibility(View.VISIBLE); editFilterLoadCheck.setVisibility(View.VISIBLE); backupRestoreCheck.setVisibility(View.VISIBLE); if (dest == null) { if (editAdditionalHostsCheck.isChecked()) dest = editAdditionalHostsCheck; + if (manuallyEditConfChk.isChecked()) + dest = manuallyEditConfChk; else if (editFilterLoadCheck.isChecked()) dest = editFilterLoadCheck; else if (backupRestoreCheck.isChecked()) @@ -1598,6 +1655,10 @@ else if (appWhiteListCheck.isChecked()) editAdditionalHostsCheck.setChecked(false); editAdditionalHostsCheck.setVisibility(View.GONE); } + if (dest != manuallyEditConfChk) { + manuallyEditConfChk.setChecked(false); + manuallyEditConfChk.setVisibility(View.GONE); + } if (dest != editFilterLoadCheck) { editFilterLoadCheck.setChecked(false); editFilterLoadCheck.setVisibility(View.GONE); @@ -1617,6 +1678,7 @@ else if (appWhiteListCheck.isChecked()) rootModeCheck.setVisibility(View.VISIBLE); enableCloakProtectCheck.setVisibility(View.VISIBLE); editAdditionalHostsCheck.setVisibility(View.VISIBLE); + manuallyEditConfChk.setVisibility(View.VISIBLE); editFilterLoadCheck.setVisibility(View.VISIBLE); if (appWhitelistingEnabled) appWhiteListCheck.setVisibility(View.VISIBLE); backupRestoreCheck.setVisibility(View.VISIBLE); @@ -1663,21 +1725,38 @@ else if (appWhiteListCheck.isChecked()) additionalHostsChanged = false; findViewById(R.id.addHostsScroll).setVisibility(View.GONE); } + if (manuallyEditConfChk.isChecked()) { + loadManuallyEditConf(); + findViewById(R.id.manuallyEditScroll).setVisibility(View.VISIBLE); + } else { + manuallyEditField.setText(""); + manuallyConfEdited = false; + findViewById(R.id.manuallyEditScroll).setVisibility(View.GONE); + } } else { setVisibilityForAdvCfg(View.VISIBLE); findViewById(R.id.filtercfgview).setVisibility(View.GONE); filterCfg.clear(); findViewById(R.id.addHostsScroll).setVisibility(View.GONE); + findViewById(R.id.manuallyEditScroll).setVisibility(View.GONE); findViewById(R.id.advSettingsScroll).setVisibility(View.GONE); appWhiteListCheck.setChecked(false); appSelector.clear(); findViewById(R.id.backupRestoreView).setVisibility(View.GONE); - editAdditionalHostsCheck.setChecked(false); editFilterLoadCheck.setChecked(false); backupRestoreCheck.setChecked(false); editAdditionalHostsCheck.setChecked(false); + manuallyEditConfChk.setChecked(false); additionalHostsField.setText(""); + manuallyEditField.setText(""); additionalHostsChanged = false; + manuallyConfEdited = false; + } + } + + private void prepareTransition(ViewGroup v) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + TransitionManager.beginDelayedTransition(v); } } @@ -1786,6 +1865,7 @@ private void setMessage(final Spanned msg, final int backgroundColor) { public void run() { link_field.setBackgroundColor(backgroundColor); link_field.setText(msg); + MSG_ACTIVE = true; } }); } @@ -1799,7 +1879,11 @@ public void closeLogger() { @Override public void afterTextChanged(Editable s) { - additionalHostsChanged = true; + + if (s == additionalHostsField.getEditableText()) + additionalHostsChanged = true; + else if (s== manuallyEditField.getEditableText()) + manuallyConfEdited = true; } diff --git a/app/src/main/java/dnsfilter/android/PaddedCheckBox.java b/app/src/main/java/dnsfilter/android/PaddedCheckBox.java index 5f3b51e5..e2b2e901 100644 --- a/app/src/main/java/dnsfilter/android/PaddedCheckBox.java +++ b/app/src/main/java/dnsfilter/android/PaddedCheckBox.java @@ -8,7 +8,7 @@ public class PaddedCheckBox extends CheckBox { - private static int dpAsPx_32 = 0; + private static int dpAsPx_40 = 0; private static int dpAsPx_10 = 0; private int convertDpToPx(int padding_in_dp) { @@ -41,14 +41,14 @@ public PaddedCheckBox(Context context, AttributeSet attrs, int defStyleAttr, int private void doPadding() { - if (dpAsPx_32 == 0) { - dpAsPx_32 = convertDpToPx(32); + if (dpAsPx_40 == 0) { + dpAsPx_40 = convertDpToPx(40); dpAsPx_10 = convertDpToPx(10); } if (Build.VERSION.SDK_INT >= 17) this.setPadding(dpAsPx_10, dpAsPx_10, dpAsPx_10, dpAsPx_10); - else this.setPadding(dpAsPx_32, 0, 0, 0); + else this.setPadding(dpAsPx_40, 0, 0, 0); } } diff --git a/app/src/main/java/dnsfilter/remote/RemoteAccessClient.java b/app/src/main/java/dnsfilter/remote/RemoteAccessClient.java index bdce72ac..8d7ec4b8 100644 --- a/app/src/main/java/dnsfilter/remote/RemoteAccessClient.java +++ b/app/src/main/java/dnsfilter/remote/RemoteAccessClient.java @@ -292,6 +292,31 @@ public void updateConfig(byte[] config) throws IOException { } } + @Override + public void updateConfigMergeDefaults(byte[] config) throws IOException { + try { + InputStream in = getInputStream(); + DataOutputStream out = new DataOutputStream(getOutputStream()); + + out.write("updateConfigMergeDefaults()\n".getBytes()); + out.writeInt(config.length); + out.write(config); + out.flush(); + + String response = Utils.readLineFromStream(in); + if (!response.equals("OK")) { + throw new ConfigurationAccessException(response, null); + } + } catch (ConfigurationAccessException e) { + connectedLogger.logLine("Remote action failed! "+e.getMessage()); + throw e; + } catch (IOException e) { + connectedLogger.logLine("Remote action updateConfig() failed! "+e.getMessage()); + closeConnectionReconnect(); + throw e; + } + } + @Override public byte[] getAdditionalHosts(int limit) throws IOException { try { diff --git a/app/src/main/java/dnsfilter/remote/RemoteAccessServer.java b/app/src/main/java/dnsfilter/remote/RemoteAccessServer.java index d4edc882..d587dfca 100644 --- a/app/src/main/java/dnsfilter/remote/RemoteAccessServer.java +++ b/app/src/main/java/dnsfilter/remote/RemoteAccessServer.java @@ -235,6 +235,12 @@ private void executeAction(String action) throws IOException { ConfigurationAccess.getLocal().updateConfig(cfg); out.write("OK\n".getBytes()); out.flush(); + } else if (action.equals("updateConfigMergeDefaults()")) { + byte[] cfg = new byte[in.readInt()]; + in.readFully(cfg); + ConfigurationAccess.getLocal().updateConfigMergeDefaults(cfg); + out.write("OK\n".getBytes()); + out.flush(); } else if (action.equals("getAdditionalHosts()")) { int limit = in.readInt(); byte[] result = ConfigurationAccess.getLocal().getAdditionalHosts(limit); diff --git a/app/src/main/java/util/PackedSortedList.java b/app/src/main/java/util/PackedSortedList.java index afe642b8..23bfbd8a 100644 --- a/app/src/main/java/util/PackedSortedList.java +++ b/app/src/main/java/util/PackedSortedList.java @@ -159,7 +159,7 @@ private synchronized void releaseDataPack() { } - private synchronized void aquireDataPack() throws FileNotFoundException { + private synchronized void acquireDataPack() throws FileNotFoundException { if (persistedPackData == null) { if (persistedPackDataRefs >0) @@ -167,7 +167,7 @@ private synchronized void aquireDataPack() throws FileNotFoundException { persistedPackData = new RandomAccessFile(persistedPackFile, "r"); } persistedPackDataRefs++; - //Logger.getLogger().logLine("Aquired reference: "+persistedPackDataRefs); + //Logger.getLogger().logLine("Acquired reference: "+persistedPackDataRefs); } @Override @@ -176,8 +176,8 @@ public boolean contains(Object key) { int pos = -1; if (!loaded) { try { - //we aquire datapack here in order to ensure it does not get closed until the binarySearch completed - aquireDataPack(); + //we acquire datapack here in order to ensure it does not get closed until the binarySearch completed + acquireDataPack(); pos = binarySearch(key); } catch (Exception e) { throw new IllegalStateException (e); @@ -207,7 +207,7 @@ public Object get(int pos) { return objMgr.bytesToObject(datapack, offs); else { try { - aquireDataPack(); + acquireDataPack(); byte[] obj = new byte[object_size]; synchronized (persistedPackData) { persistedPackData.seek(offs); diff --git a/app/src/main/java/util/conpool/Connection.java b/app/src/main/java/util/conpool/Connection.java index 026c2273..c5de204a 100644 --- a/app/src/main/java/util/conpool/Connection.java +++ b/app/src/main/java/util/conpool/Connection.java @@ -62,13 +62,13 @@ public class Connection implements TimeoutListener { private PooledConnectionOutputStream out; String poolKey; TimeoutTime timeout; - boolean aquired = true; + boolean acquired = true; boolean valid = true; boolean ssl = false; private static byte[] NO_IP = new byte[]{0,0,0,0}; private static HashMap connPooled = new HashMap(); - private static HashSet connAquired = new HashSet(); + private static HashSet connAcquired = new HashSet(); private static Hashtable CUSTOM_HOSTS = getCustomHosts(); private static String CUSTOM_HOSTS_FILE_NAME = null; private static int POOLTIMEOUT_SECONDS = 300; @@ -104,7 +104,7 @@ public static Connection connect(InetSocketAddress sadr, int conTimeout, boolean con = new Connection(sadr,conTimeout, ssl, sslSocketFactory, proxy); } con.initStreams(); - connAquired.add(con); + connAcquired.add(con); return con; } @@ -123,7 +123,7 @@ public static Connection connect(String host, int port, int conTimeout, boolean con = new Connection(host, port, conTimeout, ssl, sslSocketFactory, proxy); } con.initStreams(); - connAquired.add(con); + connAcquired.add(con); return con; } @@ -244,7 +244,7 @@ public static void invalidate() { cons[ii].release(false); } - Connection[] cons = (Connection[]) connAquired.toArray(new Connection[0]); + Connection[] cons = (Connection[]) connAcquired.toArray(new Connection[0]); for (int i = 0; i < cons.length; i++) cons[i].release(false); } @@ -252,9 +252,9 @@ public static void invalidate() { public static void poolReuse(Connection con) { synchronized (connPooled) { - if (!con.aquired) - throw new IllegalStateException("Inconsistent connection state - Cannot release non aquired connection"); - con.aquired=false; + if (!con.acquired) + throw new IllegalStateException("Inconsistent connection state - Cannot release non acquired connection"); + con.acquired=false; Vector hostCons = (Vector) connPooled.get(con.poolKey); if (hostCons == null) { hostCons = new Vector(); @@ -279,9 +279,9 @@ public static Connection poolRemove(String key) { Connection con = null; while (!found && !hostCons.isEmpty()) { con = (Connection) hostCons.remove(hostCons.size() - 1); - if (con.aquired) - throw new IllegalStateException("Inconsistent connection state - Cannot take already aquired connection from pool!"); - con.aquired=true; + if (con.acquired) + throw new IllegalStateException("Inconsistent connection state - Cannot take already acquired connection from pool!"); + con.acquired=true; toNotify.unregister(con); found = con.isAlive(); if (!found) { @@ -338,7 +338,7 @@ public void release(boolean reuse) { if (!valid) //a killed connection already released return; - connAquired.remove(this); + connAcquired.remove(this); if (reuse) { in.invalidate(); @@ -398,8 +398,8 @@ public long getTimoutTime() { // return count of received and sent bytes public long[] getTraffic() { - if (!aquired) - throw new IllegalStateException("Inconsistent connection state - Connection is not aquired!"); + if (!acquired) + throw new IllegalStateException("Inconsistent connection state - Connection is not acquired!"); return new long[] {in.getTraffic(),out.getTraffic()}; } diff --git a/app/src/main/res/drawable-night/menu_inactive.xml b/app/src/main/res/drawable-night/menu_inactive.xml index cd46f646..a8ccf62e 100644 --- a/app/src/main/res/drawable-night/menu_inactive.xml +++ b/app/src/main/res/drawable-night/menu_inactive.xml @@ -7,7 +7,7 @@ android:viewportHeight="28"> diff --git a/app/src/main/res/drawable-night/scroll_bar.xml b/app/src/main/res/drawable-night/scroll_bar.xml index 0b0d2648..a2b9e263 100644 --- a/app/src/main/res/drawable-night/scroll_bar.xml +++ b/app/src/main/res/drawable-night/scroll_bar.xml @@ -6,6 +6,6 @@ android:endColor="#474747" android:startColor="#474747" /> - + \ No newline at end of file diff --git a/app/src/main/res/drawable/arrow_left.xml b/app/src/main/res/drawable/arrow_left.xml index 6fcab473..047d7eb8 100644 --- a/app/src/main/res/drawable/arrow_left.xml +++ b/app/src/main/res/drawable/arrow_left.xml @@ -1,13 +1,13 @@ - - - - - \ No newline at end of file + android:name="path" + android:pathData="M 7.5 0 L 2.5 5 L 7.5 10 L 7.5 0 Z" + android:fillColor="#000" + android:strokeWidth="1"/> + diff --git a/app/src/main/res/drawable/arrow_right.xml b/app/src/main/res/drawable/arrow_right.xml index 9a5d191e..8682e4b9 100644 --- a/app/src/main/res/drawable/arrow_right.xml +++ b/app/src/main/res/drawable/arrow_right.xml @@ -1,13 +1,13 @@ - - - - - \ No newline at end of file + android:name="path" + android:pathData="M 2.5 10 L 7.5 5 L 2.5 0 L 2.5 10 Z" + android:fillColor="#000" + android:strokeWidth="1"/> + diff --git a/app/src/main/res/drawable/box_background.xml b/app/src/main/res/drawable/box_background.xml new file mode 100644 index 00000000..e24117f1 --- /dev/null +++ b/app/src/main/res/drawable/box_background.xml @@ -0,0 +1,9 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/menu_active.xml b/app/src/main/res/drawable/menu_active.xml index a1626f3e..c52eb977 100644 --- a/app/src/main/res/drawable/menu_active.xml +++ b/app/src/main/res/drawable/menu_active.xml @@ -7,7 +7,7 @@ android:viewportHeight="28"> diff --git a/app/src/main/res/drawable/menu_active_focus.xml b/app/src/main/res/drawable/menu_active_focus.xml index e8e82fd9..07bf7cb6 100644 --- a/app/src/main/res/drawable/menu_active_focus.xml +++ b/app/src/main/res/drawable/menu_active_focus.xml @@ -7,7 +7,7 @@ android:viewportHeight="28"> diff --git a/app/src/main/res/drawable/menu_inactive.xml b/app/src/main/res/drawable/menu_inactive.xml index 7b17f969..d5b5446c 100644 --- a/app/src/main/res/drawable/menu_inactive.xml +++ b/app/src/main/res/drawable/menu_inactive.xml @@ -7,7 +7,7 @@ android:viewportHeight="28"> diff --git a/app/src/main/res/drawable/menu_inactive_focus.xml b/app/src/main/res/drawable/menu_inactive_focus.xml index 3c4ed980..a79df406 100644 --- a/app/src/main/res/drawable/menu_inactive_focus.xml +++ b/app/src/main/res/drawable/menu_inactive_focus.xml @@ -7,7 +7,7 @@ android:viewportHeight="28"> diff --git a/app/src/main/res/drawable/scroll_bar.xml b/app/src/main/res/drawable/scroll_bar.xml index 019d332e..dde799f2 100644 --- a/app/src/main/res/drawable/scroll_bar.xml +++ b/app/src/main/res/drawable/scroll_bar.xml @@ -6,6 +6,6 @@ android:endColor="#78909c" android:startColor="#78909c" /> - + \ No newline at end of file diff --git a/app/src/main/res/layout-night/dnsconfigdialog.xml b/app/src/main/res/layout-night/dnsconfigdialog.xml index 0129453c..0645bdf6 100644 --- a/app/src/main/res/layout-night/dnsconfigdialog.xml +++ b/app/src/main/res/layout-night/dnsconfigdialog.xml @@ -37,6 +37,7 @@ android:layout_marginBottom="15dp" android:background="#000000" android:padding="10dp" + android:overScrollMode="never" android:scrollbars="none"> + + + android:drawableStart="@drawable/arrow_left" + android:paddingTop="4dp" + android:paddingBottom="4dp" + android:paddingLeft="7dp" /> + android:drawableEnd="@drawable/arrow_right" + android:paddingTop="4dp" + android:paddingBottom="4dp" + android:paddingRight="7dp"/> + android:textSize="14sp" + android:typeface="monospace"/> + + + + + + + + @@ -515,6 +568,7 @@ android:layout_marginBottom="15dp" android:background="#000000" android:scrollbarSize="5dp" + android:overScrollMode="never" android:scrollbarThumbVertical="@drawable/scroll_bar" android:visibility="gone"> @@ -570,17 +624,19 @@ android:layout_marginLeft="0dp" android:layout_marginTop="5dp" android:layout_marginBottom="3dp" - android:layout_weight="0.04" + android:layout_weight="0.085" android:background="@drawable/custom_button" - android:drawableRight="@drawable/arrow_left" - android:padding="4dp" /> + android:drawableStart="@drawable/arrow_left" + android:paddingTop="4dp" + android:paddingBottom="4dp" + android:paddingLeft="6.5dp" /> + android:drawableEnd="@drawable/arrow_right" + android:paddingTop="4dp" + android:paddingBottom="4dp" + android:paddingRight="6.5dp"/>