package com.frostnerd.smokescreen.dialog import android.content.Context import android.content.DialogInterface import android.view.View import androidx.appcompat.app.AlertDialog import com.frostnerd.smokescreen.R import com.frostnerd.smokescreen.database.entities.DnsRule import com.frostnerd.smokescreen.equalsAny import com.frostnerd.smokescreen.getPreferences import com.frostnerd.smokescreen.util.MaxSizeMap import kotlinx.android.synthetic.main.dialog_create_dnsrule.view.* import org.minidns.record.Record import java.net.Inet4Address import java.net.Inet6Address import java.util.regex.Matcher /* * Copyright (C) 2019 Daniel Wolf (Ch4t4r) * * 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 . * * You can contact the developer at daniel.wolf@frostnerd.com. */ class DnsRuleDialog(context: Context, dnsRule: DnsRule? = null, onRuleCreated: (DnsRule) -> Unit) : AlertDialog(context, context.getPreferences().theme.dialogStyle) { private var isWhitelist = false private var isBlockHost = true companion object { private val matchers = MaxSizeMap(150, 10) fun printableHost(host:String): String { return host.replace("%%", "**").replace("%", "*") } fun databaseHostToMatcher(host:String):Matcher { return matchers.getOrPut(host, { host.replace("%%", ".*").replace("%", "[^.]*").toPattern().matcher("") }) } } init { val view = layoutInflater.inflate(R.layout.dialog_create_dnsrule, null, false) setView(view) setTitle(if(dnsRule == null) R.string.dialog_newdnsrule_title else R.string.dialog_newdnsrule_edit) setButton(DialogInterface.BUTTON_NEGATIVE, context.getString(android.R.string.cancel)) { dialog, _ -> dialog.dismiss() } setButton(DialogInterface.BUTTON_POSITIVE, context.getString(android.R.string.ok)) { _, _ -> } setButton(DialogInterface.BUTTON_NEUTRAL, context.getString(R.string.dialog_newdnsrule_whitelist)) { _, _ -> } setOnShowListener { getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener { var valid = true view.host.error = null view.ipv4Til.error = null view.ipv6Til.error = null if (view.host.text.isNullOrBlank()) { view.host.error = context.getString(R.string.dialog_newdnsrule_host_invalid) valid = false } else { val ipv4Valid = isIpv4Address(view.ipv4Address.text.toString()) val ipv6Valid = isIpv6Address(view.ipv6Address.text.toString()) val bothEmpty = view.ipv4Address.text.isNullOrEmpty() && view.ipv6Address.text.isNullOrEmpty() if (!ipv4Valid || bothEmpty) { valid = false view.ipv4Address.error = context.getString(R.string.dialog_newdnsrule_ipv4_invalid) } if (!ipv6Valid || bothEmpty) { valid = false view.ipv6Address.error = context.getString(R.string.dialog_newdnsrule_ipv6_invalid) } } if (valid) { dismiss() val type = when { isWhitelist || isBlockHost -> Record.TYPE.ANY !view.ipv4Address.text.isNullOrBlank() && !view.ipv6Address.text.isNullOrBlank() -> { Record.TYPE.ANY } !view.ipv4Address.text.isNullOrBlank() -> Record.TYPE.A else -> Record.TYPE.AAAA } val primaryTarget = when { isWhitelist -> "" isBlockHost -> "0.0.0.0" else -> when (type) { Record.TYPE.A, Record.TYPE.ANY -> view.ipv4Address.text.toString() else -> view.ipv6Address.text.toString() } } val secondaryTarget = when { isWhitelist -> null isBlockHost -> "::" else -> when (type) { Record.TYPE.AAAA, Record.TYPE.ANY -> view.ipv6Address.text.toString() else -> null } } var isWildcard = false val host = view.host.text.toString().let { if(it.contains("*")) { isWildcard = true it.replace("**", "%%").replace("*", "%") } else it.replace(Regex("^www\\."), "") } val newRule = DnsRule(type, host, primaryTarget, secondaryTarget, isWildcard = isWildcard) if(dnsRule != null) newRule.id = dnsRule.id if(dnsRule == null || newRule != dnsRule) { onRuleCreated( newRule ) } } } getButton(DialogInterface.BUTTON_NEUTRAL).setOnClickListener { isWhitelist = !isWhitelist getButton(DialogInterface.BUTTON_NEUTRAL).text = if (isWhitelist) context.getString(R.string.dialog_newdnsrule_specify_address) else context.getString(R.string.dialog_newdnsrule_whitelist) val visibility = if (isWhitelist || isBlockHost) View.GONE else View.VISIBLE view.ipv4Til.visibility = visibility view.ipv6Til.visibility = visibility view.blockHost.visibility = if (isWhitelist) View.GONE else View.VISIBLE } view.blockHost.setOnCheckedChangeListener { _, isChecked -> isBlockHost = isChecked if(isBlockHost) { view.ipv4Til.visibility = View.GONE view.ipv6Til.visibility = View.GONE } else { view.ipv4Til.visibility = View.VISIBLE view.ipv6Til.visibility = View.VISIBLE } } if (dnsRule != null) { if (dnsRule.isWhitelistRule()) { isBlockHost = false isWhitelist = true view.host.setText(printableHost(dnsRule.host)) getButton(DialogInterface.BUTTON_NEUTRAL).text = context.getString(R.string.dialog_newdnsrule_specify_address) view.ipv4Til.visibility = View.GONE view.ipv6Til.visibility = View.GONE } else { view.host.setText(printableHost(dnsRule.host)) when (dnsRule.type) { Record.TYPE.A -> { view.ipv4Address.setText(dnsRule.target) view.ipv6Address.text = null } Record.TYPE.AAAA -> { view.ipv4Address.text = null view.ipv6Address.setText(dnsRule.target) } Record.TYPE.ANY -> { view.ipv4Address.setText(dnsRule.target) view.ipv6Address.setText(dnsRule.ipv6Target) } else -> {} } isBlockHost = dnsRule.target == "0.0.0.0" && dnsRule.ipv6Target?.equalsAny("::1", "::") == true } if(!isBlockHost) { view.blockHost.isChecked = false } } } } private fun isIpv4Address(text: String?): Boolean { return text.isNullOrBlank() || try { Inet4Address.getByName(text) true } catch (ex: Exception) { false } } private fun isIpv6Address(text: String?): Boolean { return text.isNullOrBlank() || try { Inet6Address.getByName(text) true } catch (ex: Exception) { false } } }