diff --git a/composeApp/src/commonMain/kotlin/org/ooni/probe/data/models/ProxyModel.kt b/composeApp/src/commonMain/kotlin/org/ooni/probe/data/models/ProxyModel.kt index fd0ae982..2e7833bb 100644 --- a/composeApp/src/commonMain/kotlin/org/ooni/probe/data/models/ProxyModel.kt +++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/data/models/ProxyModel.kt @@ -1,10 +1,38 @@ package org.ooni.probe.data.models +import kotlinx.coroutines.flow.firstOrNull import ooniprobe.composeapp.generated.resources.Res import ooniprobe.composeapp.generated.resources.Settings_Proxy_Custom import ooniprobe.composeapp.generated.resources.Settings_Proxy_None import ooniprobe.composeapp.generated.resources.Settings_Proxy_Psiphon import org.jetbrains.compose.resources.StringResource +import org.ooni.probe.data.repositories.PreferenceRepository + +const val IP_ADDRESS = ( + "((25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9])\\.(25[0-5]|2[0-4]" + + "[0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1]" + + "[0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}" + + "|[1-9][0-9]|[0-9]))" +) + +/** + * Regex for an IPv6 Address. + * + * Note that this value is adapted from the following StackOverflow answer: + * https://stackoverflow.com/a/17871737/1478764 + */ +const val IPV6_ADDRESS = + ( + "(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}" + + "|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|" + + "([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|" + + "[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4})" + + "{0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.)" + + "{3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}" + + "[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))" + ) + +const val DOMAIN_NAME = ("((?!-)[A-Za-z0-9-]{1,63}(? { + settings.protocol = ProxyProtocol.NONE + } + ProxyProtocol.PSIPHON.protocol -> { + settings.protocol = ProxyProtocol.PSIPHON + } + ProxyProtocol.SOCKS5.protocol, ProxyProtocol.HTTP.protocol, ProxyProtocol.HTTPS.protocol -> { + // ProxyProtocol.valueOf will only accept one of the values in ProxyProtocol + // as in the enum definition(uppercase). + settings.protocol = + ProxyProtocol.valueOf(protocol) + } + else -> { + // This is where we will extend the code to add support for + // more proxies, e.g., HTTP proxies. + throw InvalidProxyURL("unhandled URL scheme") + } + } + + settings.apply { + hostname = + preferenceRepository.getValueByKey(SettingsKey.PROXY_HOSTNAME).firstOrNull() as String? ?: "" + port = (preferenceRepository.getValueByKey(SettingsKey.PROXY_PORT).firstOrNull() as Int? ?: "").toString() + } + return settings + } + } + + fun getProxyString(): String { + when (protocol) { + ProxyProtocol.NONE -> return "" + ProxyProtocol.PSIPHON -> return "psiphon://" + ProxyProtocol.SOCKS5, ProxyProtocol.HTTP, ProxyProtocol.HTTPS -> { + val formattedHost = if (isIPv6(hostname)) { + "[$hostname]" + } else { + hostname + } + return "${protocol.protocol}://$formattedHost:$port/" + } + + else -> return "" + } + } + + private fun isIPv6(hostname: String): Boolean { + return IPV6_ADDRESS.toRegex().matches(hostname) + } + + class InvalidProxyURL(message: String) : Exception(message) +} diff --git a/composeApp/src/commonMain/kotlin/org/ooni/probe/data/repositories/PreferenceRepository.kt b/composeApp/src/commonMain/kotlin/org/ooni/probe/data/repositories/PreferenceRepository.kt index 41c8c719..dac852e2 100644 --- a/composeApp/src/commonMain/kotlin/org/ooni/probe/data/repositories/PreferenceRepository.kt +++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/data/repositories/PreferenceRepository.kt @@ -10,35 +10,8 @@ import androidx.datastore.preferences.core.stringPreferencesKey import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.map -import org.ooni.probe.data.models.ProxyProtocol import org.ooni.probe.data.models.SettingsKey -const val IP_ADDRESS = ( - "((25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9])\\.(25[0-5]|2[0-4]" + - "[0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1]" + - "[0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}" + - "|[1-9][0-9]|[0-9]))" -) - -/** - * Regex for an IPv6 Address. - * - * Note that this value is adapted from the following StackOverflow answer: - * https://stackoverflow.com/a/17871737/1478764 - */ -const val IPV6_ADDRESS = - ( - "(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}" + - "|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|" + - "([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|" + - "[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4})" + - "{0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.)" + - "{3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}" + - "[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))" - ) - -const val DOMAIN_NAME = ("((?!-)[A-Za-z0-9-]{1,63}(?(val preferenceKey: Preferences.Key) { class IntKey(preferenceKey: Preferences.Key) : PreferenceKey(preferenceKey) @@ -139,34 +112,6 @@ class PreferenceRepository( } } - suspend fun getProxyString(): String { - val proxyHost = - getValueByKey(SettingsKey.PROXY_HOSTNAME).firstOrNull() as String? ?: return "" - val proxyPort = getValueByKey(SettingsKey.PROXY_PORT).firstOrNull() as Int? ?: return "" - - when ( - val proxyProtocol = - getValueByKey(SettingsKey.PROXY_PROTOCOL).firstOrNull() as String? - ) { - ProxyProtocol.NONE.protocol -> return "" - ProxyProtocol.PSIPHON.protocol -> return "psiphon://" - ProxyProtocol.SOCKS5.protocol, ProxyProtocol.HTTP.protocol, ProxyProtocol.HTTPS.protocol -> { - val formattedHost = if (isIPv6(proxyHost)) { - "[$proxyHost]" - } else { - proxyHost - } - return "$proxyProtocol://$formattedHost:$proxyPort/" - } - - else -> return "" - } - } - - private fun isIPv6(hostname: String): Boolean { - return IPV6_ADDRESS.toRegex().matches(hostname) - } - suspend fun clear() { dataStore.edit { it.clear() } } diff --git a/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/settings/proxy/ProxyViewModel.kt b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/settings/proxy/ProxyViewModel.kt index 2bd5b4c1..1308cb00 100644 --- a/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/settings/proxy/ProxyViewModel.kt +++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/settings/proxy/ProxyViewModel.kt @@ -8,12 +8,12 @@ import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.update +import org.ooni.probe.data.models.DOMAIN_NAME +import org.ooni.probe.data.models.IPV6_ADDRESS +import org.ooni.probe.data.models.IP_ADDRESS import org.ooni.probe.data.models.ProxyProtocol import org.ooni.probe.data.models.ProxyType import org.ooni.probe.data.models.SettingsKey -import org.ooni.probe.data.repositories.DOMAIN_NAME -import org.ooni.probe.data.repositories.IPV6_ADDRESS -import org.ooni.probe.data.repositories.IP_ADDRESS import org.ooni.probe.data.repositories.PreferenceRepository class ProxyViewModel(