Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 94cc72d2b9 | |||
| f68c353715 | |||
| e077c18108 | |||
| 1a5e105212 | |||
| e0881caab4 | |||
| 7219425258 | |||
| 51eabe5440 | |||
| 6f0b3ce990 | |||
| 69e27ed3bb | |||
| fff6ab30e6 | |||
| fdb67a86f4 | |||
| ea088376ac | |||
| 52332d960e | |||
| 3ead542e2b | |||
| 9d1f98ff34 | |||
| f305e26a39 |
+1
-1
Submodule AndroidLibXrayLite updated: ddcaecad0a...8ad3e1ddf1
@@ -12,8 +12,8 @@ android {
|
||||
applicationId = "com.v2ray.ang"
|
||||
minSdk = 21
|
||||
targetSdk = 35
|
||||
versionCode = 654
|
||||
versionName = "1.10.4"
|
||||
versionCode = 657
|
||||
versionName = "1.10.7"
|
||||
multiDexEnabled = true
|
||||
|
||||
val abiFilterList = (properties["ABI_FILTERS"] as? String)?.split(';')
|
||||
|
||||
@@ -168,7 +168,9 @@ object AppConfig {
|
||||
// Android Private DNS constants
|
||||
const val DNS_DNSPOD_DOMAIN = "dot.pub"
|
||||
const val DNS_ALIDNS_DOMAIN = "dns.alidns.com"
|
||||
const val DNS_CLOUDFLARE_DOMAIN = "one.one.one.one"
|
||||
const val DNS_CLOUDFLARE_ONE_DOMAIN = "one.one.one.one"
|
||||
const val DNS_CLOUDFLARE_DNS_COM_DOMAIN = "dns.cloudflare.com"
|
||||
const val DNS_CLOUDFLARE_DNS_DOMAIN = "cloudflare-dns.com"
|
||||
const val DNS_GOOGLE_DOMAIN = "dns.google"
|
||||
const val DNS_QUAD9_DOMAIN = "dns.quad9.net"
|
||||
const val DNS_YANDEX_DOMAIN = "common.dot.dns.yandex.net"
|
||||
@@ -182,14 +184,16 @@ object AppConfig {
|
||||
const val HEADER_TYPE_HTTP = "http"
|
||||
|
||||
val DNS_ALIDNS_ADDRESSES = arrayListOf("223.5.5.5", "223.6.6.6", "2400:3200::1", "2400:3200:baba::1")
|
||||
val DNS_CLOUDFLARE_ADDRESSES = arrayListOf("1.1.1.1", "1.0.0.1", "2606:4700:4700::1111", "2606:4700:4700::1001")
|
||||
val DNS_CLOUDFLARE_ONE_ADDRESSES = arrayListOf("1.1.1.1", "1.0.0.1", "2606:4700:4700::1111", "2606:4700:4700::1001")
|
||||
val DNS_CLOUDFLARE_DNS_COM_ADDRESSES = arrayListOf("104.16.132.229", "104.16.133.229", "2606:4700::6810:84e5", "2606:4700::6810:85e5")
|
||||
val DNS_CLOUDFLARE_DNS_ADDRESSES = arrayListOf("104.16.248.249", "104.16.249.249", "2606:4700::6810:f8f9", "2606:4700::6810:f9f9")
|
||||
val DNS_DNSPOD_ADDRESSES = arrayListOf("1.12.12.12", "120.53.53.53")
|
||||
val DNS_GOOGLE_ADDRESSES = arrayListOf("8.8.8.8", "8.8.4.4", "2001:4860:4860::8888", "2001:4860:4860::8844")
|
||||
val DNS_QUAD9_ADDRESSES = arrayListOf("9.9.9.9", "149.112.112.112", "2620:fe::fe", "2620:fe::9")
|
||||
val DNS_YANDEX_ADDRESSES = arrayListOf("77.88.8.8", "77.88.8.1", "2a02:6b8::feed:0ff", "2a02:6b8:0:1::feed:0ff")
|
||||
|
||||
//minimum list https://serverfault.com/a/304791
|
||||
val BYPASS_PRIVATE_IP_LIST = arrayListOf(
|
||||
val ROUTED_IP_LIST = arrayListOf(
|
||||
"0.0.0.0/5",
|
||||
"8.0.0.0/7",
|
||||
"11.0.0.0/8",
|
||||
|
||||
@@ -4,6 +4,7 @@ import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.dto.NetworkType
|
||||
import com.v2ray.ang.dto.ProfileItem
|
||||
import com.v2ray.ang.extension.isNotNullEmpty
|
||||
import com.v2ray.ang.util.HttpUtil
|
||||
import com.v2ray.ang.util.Utils
|
||||
import java.net.URI
|
||||
|
||||
@@ -26,7 +27,7 @@ open class FmtBase {
|
||||
val url = String.format(
|
||||
"%s@%s:%s",
|
||||
Utils.urlEncode(userInfo ?: ""),
|
||||
Utils.getIpv6Address(config.server),
|
||||
Utils.getIpv6Address(HttpUtil.toIdnDomain(config.server.orEmpty())),
|
||||
config.serverPort
|
||||
)
|
||||
|
||||
@@ -149,6 +150,8 @@ open class FmtBase {
|
||||
return dicQuery
|
||||
}
|
||||
|
||||
|
||||
fun getServerAddress(profileItem: ProfileItem): String {
|
||||
return HttpUtil.toIdnDomain(profileItem.server.orEmpty())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ object HttpFmt : FmtBase() {
|
||||
val outboundBean = V2rayConfigManager.createInitOutbound(EConfigType.HTTP)
|
||||
|
||||
outboundBean?.settings?.servers?.first()?.let { server ->
|
||||
server.address = profileItem.server.orEmpty()
|
||||
server.address = getServerAddress(profileItem)
|
||||
server.port = profileItem.serverPort.orEmpty().toInt()
|
||||
if (profileItem.username.isNotNullEmpty()) {
|
||||
val socksUsersBean = OutboundBean.OutSettingsBean.ServersBean.SocksUsersBean()
|
||||
|
||||
@@ -135,7 +135,7 @@ object ShadowsocksFmt : FmtBase() {
|
||||
val outboundBean = V2rayConfigManager.createInitOutbound(EConfigType.SHADOWSOCKS)
|
||||
|
||||
outboundBean?.settings?.servers?.first()?.let { server ->
|
||||
server.address = profileItem.server.orEmpty()
|
||||
server.address = getServerAddress(profileItem)
|
||||
server.port = profileItem.serverPort.orEmpty().toInt()
|
||||
server.password = profileItem.password
|
||||
server.method = profileItem.method
|
||||
|
||||
@@ -64,7 +64,7 @@ object SocksFmt : FmtBase() {
|
||||
val outboundBean = V2rayConfigManager.createInitOutbound(EConfigType.SOCKS)
|
||||
|
||||
outboundBean?.settings?.servers?.first()?.let { server ->
|
||||
server.address = profileItem.server.orEmpty()
|
||||
server.address = getServerAddress(profileItem)
|
||||
server.port = profileItem.serverPort.orEmpty().toInt()
|
||||
if (profileItem.username.isNotNullEmpty()) {
|
||||
val socksUsersBean = OutboundBean.OutSettingsBean.ServersBean.SocksUsersBean()
|
||||
|
||||
@@ -64,7 +64,7 @@ object TrojanFmt : FmtBase() {
|
||||
val outboundBean = V2rayConfigManager.createInitOutbound(EConfigType.TROJAN)
|
||||
|
||||
outboundBean?.settings?.servers?.first()?.let { server ->
|
||||
server.address = profileItem.server.orEmpty()
|
||||
server.address = getServerAddress(profileItem)
|
||||
server.port = profileItem.serverPort.orEmpty().toInt()
|
||||
server.password = profileItem.password
|
||||
server.flow = profileItem.flow
|
||||
|
||||
@@ -60,7 +60,7 @@ object VlessFmt : FmtBase() {
|
||||
val outboundBean = V2rayConfigManager.createInitOutbound(EConfigType.VLESS)
|
||||
|
||||
outboundBean?.settings?.vnext?.first()?.let { vnext ->
|
||||
vnext.address = profileItem.server.orEmpty()
|
||||
vnext.address = getServerAddress(profileItem)
|
||||
vnext.port = profileItem.serverPort.orEmpty().toInt()
|
||||
vnext.users[0].id = profileItem.password.orEmpty()
|
||||
vnext.users[0].encryption = profileItem.method
|
||||
|
||||
@@ -172,7 +172,7 @@ object VmessFmt : FmtBase() {
|
||||
val outboundBean = V2rayConfigManager.createInitOutbound(EConfigType.VMESS)
|
||||
|
||||
outboundBean?.settings?.vnext?.first()?.let { vnext ->
|
||||
vnext.address = profileItem.server.orEmpty()
|
||||
vnext.address = getServerAddress(profileItem)
|
||||
vnext.port = profileItem.serverPort.orEmpty().toInt()
|
||||
vnext.users[0].id = profileItem.password.orEmpty()
|
||||
vnext.users[0].security = profileItem.method
|
||||
|
||||
@@ -415,7 +415,7 @@ object AngConfigManager {
|
||||
if (!it.second.enabled) {
|
||||
return 0
|
||||
}
|
||||
val url = HttpUtil.idnToASCII(it.second.url)
|
||||
val url = HttpUtil.toIdnUrl(it.second.url)
|
||||
if (!Utils.isValidUrl(url)) {
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -159,7 +159,7 @@ object SettingsManager {
|
||||
* @return True if bypassing LAN, false otherwise.
|
||||
*/
|
||||
fun routingRulesetsBypassLan(): Boolean {
|
||||
val vpnBypassLan = MmkvManager.decodeSettingsString(AppConfig.PREF_VPN_BYPASS_LAN) ?: "0"
|
||||
val vpnBypassLan = MmkvManager.decodeSettingsString(AppConfig.PREF_VPN_BYPASS_LAN) ?: "1"
|
||||
if (vpnBypassLan == "1") {
|
||||
return true
|
||||
} else if (vpnBypassLan == "2") {
|
||||
|
||||
@@ -17,7 +17,6 @@ import java.io.FileOutputStream
|
||||
|
||||
object UpdateCheckerManager {
|
||||
suspend fun checkForUpdate(includePreRelease: Boolean = false): CheckUpdateResult = withContext(Dispatchers.IO) {
|
||||
try {
|
||||
val url = if (includePreRelease) {
|
||||
AppConfig.APP_API_URL
|
||||
} else {
|
||||
@@ -53,10 +52,6 @@ object UpdateCheckerManager {
|
||||
} else {
|
||||
CheckUpdateResult(hasUpdate = false)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(AppConfig.TAG, "Failed to check for updates: ${e.message}")
|
||||
return@withContext CheckUpdateResult(hasUpdate = false, error = e.message)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun downloadApk(context: Context, downloadUrl: String): File? = withContext(Dispatchers.IO) {
|
||||
|
||||
@@ -16,7 +16,6 @@ import com.v2ray.ang.dto.V2rayConfig.OutboundBean.StreamSettingsBean
|
||||
import com.v2ray.ang.dto.V2rayConfig.RoutingBean.RulesBean
|
||||
import com.v2ray.ang.extension.isNotNullEmpty
|
||||
import com.v2ray.ang.fmt.HttpFmt
|
||||
import com.v2ray.ang.fmt.Hysteria2Fmt
|
||||
import com.v2ray.ang.fmt.ShadowsocksFmt
|
||||
import com.v2ray.ang.fmt.SocksFmt
|
||||
import com.v2ray.ang.fmt.TrojanFmt
|
||||
@@ -479,6 +478,25 @@ object V2rayConfigManager {
|
||||
)
|
||||
}
|
||||
|
||||
//block dns
|
||||
val blkDomain = getUserRule2Domain(AppConfig.TAG_BLOCKED)
|
||||
if (blkDomain.isNotEmpty()) {
|
||||
hosts.putAll(blkDomain.map { it to AppConfig.LOOPBACK })
|
||||
}
|
||||
|
||||
// hardcode googleapi rule to fix play store problems
|
||||
hosts[AppConfig.GOOGLEAPIS_CN_DOMAIN] = AppConfig.GOOGLEAPIS_COM_DOMAIN
|
||||
|
||||
// hardcode popular Android Private DNS rule to fix localhost DNS problem
|
||||
hosts[AppConfig.DNS_ALIDNS_DOMAIN] = AppConfig.DNS_ALIDNS_ADDRESSES
|
||||
hosts[AppConfig.DNS_CLOUDFLARE_ONE_DOMAIN] = AppConfig.DNS_CLOUDFLARE_ONE_ADDRESSES
|
||||
hosts[AppConfig.DNS_CLOUDFLARE_DNS_COM_DOMAIN] = AppConfig.DNS_CLOUDFLARE_DNS_COM_ADDRESSES
|
||||
hosts[AppConfig.DNS_CLOUDFLARE_DNS_DOMAIN] = AppConfig.DNS_CLOUDFLARE_DNS_ADDRESSES
|
||||
hosts[AppConfig.DNS_DNSPOD_DOMAIN] = AppConfig.DNS_DNSPOD_ADDRESSES
|
||||
hosts[AppConfig.DNS_GOOGLE_DOMAIN] = AppConfig.DNS_GOOGLE_ADDRESSES
|
||||
hosts[AppConfig.DNS_QUAD9_DOMAIN] = AppConfig.DNS_QUAD9_ADDRESSES
|
||||
hosts[AppConfig.DNS_YANDEX_DOMAIN] = AppConfig.DNS_YANDEX_ADDRESSES
|
||||
|
||||
//User DNS hosts
|
||||
try {
|
||||
val userHosts = MmkvManager.decodeSettingsString(AppConfig.PREF_DNS_HOSTS)
|
||||
@@ -493,24 +511,6 @@ object V2rayConfigManager {
|
||||
Log.e(AppConfig.TAG, "Failed to configure user DNS hosts", e)
|
||||
}
|
||||
|
||||
//block dns
|
||||
val blkDomain = getUserRule2Domain(AppConfig.TAG_BLOCKED)
|
||||
if (blkDomain.isNotEmpty()) {
|
||||
hosts.putAll(blkDomain.map { it to AppConfig.LOOPBACK })
|
||||
}
|
||||
|
||||
// hardcode googleapi rule to fix play store problems
|
||||
hosts[AppConfig.GOOGLEAPIS_CN_DOMAIN] = AppConfig.GOOGLEAPIS_COM_DOMAIN
|
||||
|
||||
// hardcode popular Android Private DNS rule to fix localhost DNS problem
|
||||
hosts[AppConfig.DNS_ALIDNS_DOMAIN] = AppConfig.DNS_ALIDNS_ADDRESSES
|
||||
hosts[AppConfig.DNS_CLOUDFLARE_DOMAIN] = AppConfig.DNS_CLOUDFLARE_ADDRESSES
|
||||
hosts[AppConfig.DNS_DNSPOD_DOMAIN] = AppConfig.DNS_DNSPOD_ADDRESSES
|
||||
hosts[AppConfig.DNS_GOOGLE_DOMAIN] = AppConfig.DNS_GOOGLE_ADDRESSES
|
||||
hosts[AppConfig.DNS_QUAD9_DOMAIN] = AppConfig.DNS_QUAD9_ADDRESSES
|
||||
hosts[AppConfig.DNS_YANDEX_DOMAIN] = AppConfig.DNS_YANDEX_ADDRESSES
|
||||
|
||||
|
||||
// DNS dns
|
||||
v2rayConfig.dns = V2rayConfig.DnsBean(
|
||||
servers = servers,
|
||||
@@ -828,7 +828,11 @@ object V2rayConfigManager {
|
||||
for (item in proxyOutboundList) {
|
||||
val domain = item.getServerAddress()
|
||||
if (domain.isNullOrEmpty()) continue
|
||||
if (newHosts.containsKey(domain)) continue
|
||||
|
||||
if (newHosts.containsKey(domain)) {
|
||||
item.ensureSockopt().domainStrategy = if (preferIpv6) "UseIPv6v4" else "UseIPv4v6"
|
||||
continue
|
||||
}
|
||||
|
||||
val resolvedIps = HttpUtil.resolveHostToIP(domain, preferIpv6)
|
||||
if (resolvedIps.isNullOrEmpty()) continue
|
||||
|
||||
@@ -25,14 +25,13 @@ class QSTileService : TileService() {
|
||||
* @param state The state to set.
|
||||
*/
|
||||
fun setState(state: Int) {
|
||||
qsTile?.icon = Icon.createWithResource(applicationContext, R.drawable.ic_stat_name)
|
||||
if (state == Tile.STATE_INACTIVE) {
|
||||
qsTile?.state = Tile.STATE_INACTIVE
|
||||
qsTile?.label = getString(R.string.app_name)
|
||||
qsTile?.icon = Icon.createWithResource(applicationContext, R.drawable.ic_stat_name)
|
||||
} else if (state == Tile.STATE_ACTIVE) {
|
||||
qsTile?.state = Tile.STATE_ACTIVE
|
||||
qsTile?.label = V2RayServiceManager.getRunningServerName()
|
||||
qsTile?.icon = Icon.createWithResource(applicationContext, R.drawable.ic_stat_name)
|
||||
}
|
||||
|
||||
qsTile?.updateTile()
|
||||
@@ -45,7 +44,11 @@ class QSTileService : TileService() {
|
||||
override fun onStartListening() {
|
||||
super.onStartListening()
|
||||
|
||||
setState(Tile.STATE_INACTIVE)
|
||||
if (V2RayServiceManager.isRunning()) {
|
||||
setState(Tile.STATE_ACTIVE)
|
||||
} else {
|
||||
setState(Tile.STATE_INACTIVE)
|
||||
}
|
||||
mMsgReceive = ReceiveMessageHandler(this)
|
||||
val mFilter = IntentFilter(AppConfig.BROADCAST_ACTION_ACTIVITY)
|
||||
ContextCompat.registerReceiver(applicationContext, mMsgReceive, mFilter, Utils.receiverFlags())
|
||||
|
||||
@@ -167,7 +167,7 @@ class V2RayVpnService : VpnService(), ServiceControl {
|
||||
//builder.addDnsServer(PRIVATE_VLAN4_ROUTER)
|
||||
val bypassLan = SettingsManager.routingRulesetsBypassLan()
|
||||
if (bypassLan) {
|
||||
AppConfig.BYPASS_PRIVATE_IP_LIST.forEach {
|
||||
AppConfig.ROUTED_IP_LIST.forEach {
|
||||
val addr = it.split('/')
|
||||
builder.addRoute(addr[0], addr[1].toInt())
|
||||
}
|
||||
@@ -179,6 +179,7 @@ class V2RayVpnService : VpnService(), ServiceControl {
|
||||
builder.addAddress(PRIVATE_VLAN6_CLIENT, 126)
|
||||
if (bypassLan) {
|
||||
builder.addRoute("2000::", 3) //currently only 1/8 of total ipV6 is in use
|
||||
builder.addRoute("fc00::", 18) //Xray-core default FakeIPv6 Pool
|
||||
} else {
|
||||
builder.addRoute("::", 0)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.v2ray.ang.ui
|
||||
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.v2ray.ang.AppConfig
|
||||
@@ -9,6 +10,7 @@ import com.v2ray.ang.R
|
||||
import com.v2ray.ang.databinding.ActivityCheckUpdateBinding
|
||||
import com.v2ray.ang.dto.CheckUpdateResult
|
||||
import com.v2ray.ang.extension.toast
|
||||
import com.v2ray.ang.extension.toastError
|
||||
import com.v2ray.ang.extension.toastSuccess
|
||||
import com.v2ray.ang.handler.MmkvManager
|
||||
import com.v2ray.ang.handler.SpeedtestManager
|
||||
@@ -46,11 +48,16 @@ class CheckUpdateActivity : BaseActivity() {
|
||||
toast(R.string.update_checking_for_update)
|
||||
|
||||
lifecycleScope.launch {
|
||||
val result = UpdateCheckerManager.checkForUpdate(includePreRelease)
|
||||
if (result.hasUpdate) {
|
||||
showUpdateDialog(result)
|
||||
} else {
|
||||
toastSuccess(R.string.update_already_latest_version)
|
||||
try {
|
||||
val result = UpdateCheckerManager.checkForUpdate(includePreRelease)
|
||||
if (result.hasUpdate) {
|
||||
showUpdateDialog(result)
|
||||
} else {
|
||||
toastSuccess(R.string.update_already_latest_version)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(AppConfig.TAG, "Failed to check for updates: ${e.message}")
|
||||
toastError(e.message ?: getString(R.string.toast_failure))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,12 +18,14 @@ import java.net.URL
|
||||
object HttpUtil {
|
||||
|
||||
/**
|
||||
* Converts a URL string to its ASCII representation.
|
||||
* Converts the domain part of a URL string to its IDN (Punycode, ASCII Compatible Encoding) format.
|
||||
*
|
||||
* @param str The URL string to convert.
|
||||
* @return The ASCII representation of the URL.
|
||||
* For example, a URL like "https://例子.中国/path" will be converted to "https://xn--fsqu00a.xn--fiqs8s/path".
|
||||
*
|
||||
* @param str The URL string to convert (can contain non-ASCII characters in the domain).
|
||||
* @return The URL string with the domain part converted to ASCII-compatible (Punycode) format.
|
||||
*/
|
||||
fun idnToASCII(str: String): String {
|
||||
fun toIdnUrl(str: String): String {
|
||||
val url = URL(str)
|
||||
val host = url.host
|
||||
val asciiHost = IDN.toASCII(url.host, IDN.ALLOW_UNASSIGNED)
|
||||
@@ -34,6 +36,28 @@ object HttpUtil {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a Unicode domain name to its IDN (Punycode, ASCII Compatible Encoding) format.
|
||||
* If the input is an IP address or already an ASCII domain, returns the original string.
|
||||
*
|
||||
* @param domain The domain string to convert (can include non-ASCII internationalized characters).
|
||||
* @return The domain in ASCII-compatible (Punycode) format, or the original string if input is an IP or already ASCII.
|
||||
*/
|
||||
fun toIdnDomain(domain: String): String {
|
||||
// Return as is if it's a pure IP address (IPv4 or IPv6)
|
||||
if (Utils.isPureIpAddress(domain)) {
|
||||
return domain
|
||||
}
|
||||
|
||||
// Return as is if already ASCII (English domain or already punycode)
|
||||
if (domain.all { it.code < 128 }) {
|
||||
return domain
|
||||
}
|
||||
|
||||
// Otherwise, convert to ASCII using IDN
|
||||
return IDN.toASCII(domain, IDN.ALLOW_UNASSIGNED)
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a hostname to an IP address, returns original input if it's already an IP
|
||||
*
|
||||
|
||||
@@ -28,13 +28,17 @@ object PluginUtil {
|
||||
fun runPlugin(context: Context, config: ProfileItem?, socksPort: Int?) {
|
||||
Log.i(AppConfig.TAG, "Starting plugin execution")
|
||||
|
||||
if (config == null || socksPort == null) {
|
||||
if (config == null) {
|
||||
Log.w(AppConfig.TAG, "Cannot run plugin: config is null")
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
if (config.configType == EConfigType.HYSTERIA2) {
|
||||
if (socksPort == null) {
|
||||
Log.w(AppConfig.TAG, "Cannot run plugin: socksPort is null")
|
||||
return
|
||||
}
|
||||
Log.i(AppConfig.TAG, "Running Hysteria2 plugin")
|
||||
val configFile = genConfigHy2(context, config, socksPort) ?: return
|
||||
val cmd = genCmdHy2(context, configFile)
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
android:title="@string/title_pref_vpn_dns" />
|
||||
|
||||
<ListPreference
|
||||
android:defaultValue="0"
|
||||
android:defaultValue="1"
|
||||
android:entries="@array/vpn_bypass_lan"
|
||||
android:entryValues="@array/vpn_bypass_lan_value"
|
||||
android:key="pref_vpn_bypass_lan"
|
||||
|
||||
@@ -10,31 +10,31 @@ class HttpUtilTest {
|
||||
fun testIdnToASCII() {
|
||||
// Regular URL remains unchanged
|
||||
val regularUrl = "https://example.com/path"
|
||||
assertEquals(regularUrl, HttpUtil.idnToASCII(regularUrl))
|
||||
assertEquals(regularUrl, HttpUtil.toIdnUrl(regularUrl))
|
||||
|
||||
// Non-ASCII URL converts to ASCII (Punycode)
|
||||
val nonAsciiUrl = "https://例子.测试/path"
|
||||
val expectedNonAscii = "https://xn--fsqu00a.xn--0zwm56d/path"
|
||||
assertEquals(expectedNonAscii, HttpUtil.idnToASCII(nonAsciiUrl))
|
||||
assertEquals(expectedNonAscii, HttpUtil.toIdnUrl(nonAsciiUrl))
|
||||
|
||||
// Mixed URL only converts the host part
|
||||
val mixedUrl = "https://例子.com/测试"
|
||||
val expectedMixed = "https://xn--fsqu00a.com/测试"
|
||||
assertEquals(expectedMixed, HttpUtil.idnToASCII(mixedUrl))
|
||||
assertEquals(expectedMixed, HttpUtil.toIdnUrl(mixedUrl))
|
||||
|
||||
// URL with Basic Authentication using regular domain
|
||||
val basicAuthUrl = "https://user:password@example.com/path"
|
||||
assertEquals(basicAuthUrl, HttpUtil.idnToASCII(basicAuthUrl))
|
||||
assertEquals(basicAuthUrl, HttpUtil.toIdnUrl(basicAuthUrl))
|
||||
|
||||
// URL with Basic Authentication using non-ASCII domain
|
||||
val basicAuthNonAscii = "https://user:password@例子.测试/path"
|
||||
val expectedBasicAuthNonAscii = "https://user:password@xn--fsqu00a.xn--0zwm56d/path"
|
||||
assertEquals(expectedBasicAuthNonAscii, HttpUtil.idnToASCII(basicAuthNonAscii))
|
||||
assertEquals(expectedBasicAuthNonAscii, HttpUtil.toIdnUrl(basicAuthNonAscii))
|
||||
|
||||
// URL with non-ASCII username and password
|
||||
val nonAsciiAuth = "https://用户:密码@example.com/path"
|
||||
// Basic auth credentials should remain unchanged as they're percent-encoded separately
|
||||
assertEquals(nonAsciiAuth, HttpUtil.idnToASCII(nonAsciiAuth))
|
||||
assertEquals(nonAsciiAuth, HttpUtil.toIdnUrl(nonAsciiAuth))
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[versions]
|
||||
agp = "8.10.0"
|
||||
agp = "8.10.1"
|
||||
desugarJdkLibs = "2.1.5"
|
||||
gradleLicensePlugin = "0.9.8"
|
||||
kotlin = "2.1.21"
|
||||
@@ -7,7 +7,7 @@ coreKtx = "1.16.0"
|
||||
junit = "4.13.2"
|
||||
junitVersion = "1.2.1"
|
||||
espressoCore = "3.6.1"
|
||||
appcompat = "1.7.0"
|
||||
appcompat = "1.7.1"
|
||||
material = "1.12.0"
|
||||
activity = "1.10.1"
|
||||
constraintlayout = "2.2.1"
|
||||
@@ -21,7 +21,7 @@ toasty = "1.5.2"
|
||||
editorkit = "2.9.0"
|
||||
core = "3.5.3"
|
||||
workRuntimeKtx = "2.10.1"
|
||||
lifecycleViewmodelKtx = "2.9.0"
|
||||
lifecycleViewmodelKtx = "2.9.1"
|
||||
multidex = "2.0.1"
|
||||
mockitoMockitoInline = "5.2.0"
|
||||
flexbox = "3.0.0"
|
||||
|
||||
Reference in New Issue
Block a user