Skip to content

Commit

Permalink
chore: extract ProxySettings class
Browse files Browse the repository at this point in the history
  • Loading branch information
aanorbel committed Aug 23, 2024
1 parent ff5baad commit 90682eb
Show file tree
Hide file tree
Showing 3 changed files with 114 additions and 58 deletions.
Original file line number Diff line number Diff line change
@@ -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}(?<!-)\\.)+[A-Za-z]{2,6}")

enum class ProxyType(val label: StringResource, val value: String) {
NONE(
Expand Down Expand Up @@ -41,3 +69,86 @@ enum class ProxyProtocol(val protocol: String) {
}
}
}

/**
* ProxySettings contains the settings configured inside the proxy. Please, see the
* documentation of proxy activity for the design rationale.
*/
class ProxySettings {
/**
* Scheme is the proxy scheme (e.g., "psiphon", "socks5").
*/
var protocol: ProxyProtocol = ProxyProtocol.NONE

/**
* Hostname is the hostname for custom proxies.
*/
var hostname: String = ""

/**
* Port is the port for custom proxies.
*/
var port: String = ""

companion object {
/**
* Creates a new ProxySettings object from the values stored in the preference repository.
* @param preferenceRepository the [PreferenceRepository].
*/
suspend fun newProxySettings(preferenceRepository: PreferenceRepository): ProxySettings {
val protocol =
preferenceRepository.getValueByKey(SettingsKey.PROXY_PROTOCOL).firstOrNull() as String?
val settings = ProxySettings()

when (protocol) {
ProxyProtocol.NONE.protocol -> {
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)
}
Original file line number Diff line number Diff line change
Expand Up @@ -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}(?<!-)\\.)+[A-Za-z]{2,6}")

sealed class PreferenceKey<T>(val preferenceKey: Preferences.Key<T>) {
class IntKey(preferenceKey: Preferences.Key<Int>) : PreferenceKey<Int>(preferenceKey)

Expand Down Expand Up @@ -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() }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down

0 comments on commit 90682eb

Please sign in to comment.