DnsRuleDialog.kt 8.79 KB
Newer Older
1
2
3
package com.frostnerd.smokescreen.dialog

import android.content.Context
4
import android.content.DialogInterface
5
import android.view.View
6
import androidx.appcompat.app.AlertDialog
7
import com.frostnerd.smokescreen.R
8
import com.frostnerd.smokescreen.database.entities.DnsRule
9
import com.frostnerd.smokescreen.equalsAny
10
import com.frostnerd.smokescreen.getPreferences
11
import com.frostnerd.smokescreen.util.MaxSizeMap
12
13
14
15
import kotlinx.android.synthetic.main.dialog_create_dnsrule.view.*
import org.minidns.record.Record
import java.net.Inet4Address
import java.net.Inet6Address
16
import java.util.regex.Matcher
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

/*
 * 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 <http://www.gnu.org/licenses/>.
 *
 * You can contact the developer at daniel.wolf@frostnerd.com.
 */
36
37
class DnsRuleDialog(context: Context, dnsRule: DnsRule? = null, onRuleCreated: (DnsRule) -> Unit) :
    AlertDialog(context, context.getPreferences().theme.dialogStyle) {
38
    private var isWhitelist = false
39
    private var isBlockHost = true
40
    companion object {
41
        private val matchers = MaxSizeMap<String, Matcher>(150, 10)
42
43
44
45
46
47
48
49
50
51
52

        fun printableHost(host:String): String {
            return host.replace("%%", "**").replace("%", "*")
        }

        fun databaseHostToMatcher(host:String):Matcher {
            return matchers.getOrPut(host, {
                host.replace("%%", ".*").replace("%", "[^.]*").toPattern().matcher("")
            })
        }
    }
53

54
55
56
    init {
        val view = layoutInflater.inflate(R.layout.dialog_create_dnsrule, null, false)
        setView(view)
57
        setTitle(if(dnsRule == null) R.string.dialog_newdnsrule_title else R.string.dialog_newdnsrule_edit)
Daniel Wolf's avatar
Daniel Wolf committed
58
        setButton(DialogInterface.BUTTON_NEGATIVE, context.getString(android.R.string.cancel)) { dialog, _ ->
59
60
61
62
            dialog.dismiss()
        }
        setButton(DialogInterface.BUTTON_POSITIVE, context.getString(android.R.string.ok)) { _, _ ->

63
64
65
        }
        setButton(DialogInterface.BUTTON_NEUTRAL, context.getString(R.string.dialog_newdnsrule_whitelist)) { _, _ ->

66
67
68
69
70
71
72
        }
        setOnShowListener {
            getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener {
                var valid = true
                view.host.error = null
                view.ipv4Til.error = null
                view.ipv6Til.error = null
73
                if (view.host.text.isNullOrBlank()) {
74
75
76
77
78
79
                    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()
80
                    if (!ipv4Valid || bothEmpty) {
81
82
83
                        valid = false
                        view.ipv4Address.error = context.getString(R.string.dialog_newdnsrule_ipv4_invalid)
                    }
84
                    if (!ipv6Valid || bothEmpty) {
85
86
87
88
89
90
                        valid = false
                        view.ipv6Address.error = context.getString(R.string.dialog_newdnsrule_ipv6_invalid)
                    }
                }
                if (valid) {
                    dismiss()
91
                    val type = when {
92
                        isWhitelist || isBlockHost -> Record.TYPE.ANY
93
94
95
96
97
98
                        !view.ipv4Address.text.isNullOrBlank() && !view.ipv6Address.text.isNullOrBlank() -> {
                            Record.TYPE.ANY
                        }
                        !view.ipv4Address.text.isNullOrBlank() -> Record.TYPE.A
                        else -> Record.TYPE.AAAA
                    }
Daniel Wolf's avatar
Daniel Wolf committed
99
100
101
102
103
104
105
                    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()
                        }
106
                    }
Daniel Wolf's avatar
Daniel Wolf committed
107
108
109
110
111
112
113
                    val secondaryTarget = when {
                        isWhitelist -> null
                        isBlockHost -> "::"
                        else -> when (type) {
                            Record.TYPE.AAAA, Record.TYPE.ANY -> view.ipv6Address.text.toString()
                            else -> null
                        }
114
                    }
115
116
117
118
                    var isWildcard = false
                    val host = view.host.text.toString().let {
                        if(it.contains("*")) {
                            isWildcard = true
119
                            it.replace("**", "%%").replace("*", "%")
120
                        } else it.replace(Regex("^www\\."), "")
121
122
                    }

123
124
                    val newRule = DnsRule(type, host, primaryTarget, secondaryTarget, isWildcard = isWildcard)
                    if(dnsRule != null) newRule.id = dnsRule.id
125
126
127
128
129
                    if(dnsRule == null || newRule != dnsRule) {
                        onRuleCreated(
                            newRule
                        )
                    }
130
131
                }
            }
132
133
134
135
136
137
            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)

138
                val visibility = if (isWhitelist || isBlockHost) View.GONE else View.VISIBLE
139
140
                view.ipv4Til.visibility = visibility
                view.ipv6Til.visibility = visibility
141
142
143
144
145
146
147
148
149
150
151
                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
                }
152
153
154
            }
            if (dnsRule != null) {
                if (dnsRule.isWhitelistRule()) {
155
                    isBlockHost = false
156
                    isWhitelist = true
157
                    view.host.setText(printableHost(dnsRule.host))
158
159
160
161
162
                    getButton(DialogInterface.BUTTON_NEUTRAL).text =
                        context.getString(R.string.dialog_newdnsrule_specify_address)
                    view.ipv4Til.visibility = View.GONE
                    view.ipv6Til.visibility = View.GONE
                } else {
163
                    view.host.setText(printableHost(dnsRule.host))
Daniel Wolf's avatar
Daniel Wolf committed
164
165
                    when (dnsRule.type) {
                        Record.TYPE.A -> {
166
167
168
                            view.ipv4Address.setText(dnsRule.target)
                            view.ipv6Address.text = null
                        }
Daniel Wolf's avatar
Daniel Wolf committed
169
                        Record.TYPE.AAAA -> {
170
171
172
                            view.ipv4Address.text = null
                            view.ipv6Address.setText(dnsRule.target)
                        }
Daniel Wolf's avatar
Daniel Wolf committed
173
                        Record.TYPE.ANY -> {
174
175
176
                            view.ipv4Address.setText(dnsRule.target)
                            view.ipv6Address.setText(dnsRule.ipv6Target)
                        }
Daniel Wolf's avatar
Daniel Wolf committed
177
                        else -> {}
178
                    }
179
                    isBlockHost = dnsRule.target == "0.0.0.0" && dnsRule.ipv6Target?.equalsAny("::1", "::") == true
Daniel Wolf's avatar
Daniel Wolf committed
180
181
182
                }
                if(!isBlockHost) {
                    view.blockHost.isChecked = false
183
184
                }
            }
185
186
187
        }
    }

188
    private fun isIpv4Address(text: String?): Boolean {
189
190
191
        return text.isNullOrBlank() || try {
            Inet4Address.getByName(text)
            true
192
        } catch (ex: Exception) {
193
194
195
196
            false
        }
    }

197
    private fun isIpv6Address(text: String?): Boolean {
198
199
200
        return text.isNullOrBlank() || try {
            Inet6Address.getByName(text)
            true
201
        } catch (ex: Exception) {
202
203
204
205
            false
        }
    }
}