mirror of
https://github.com/2dust/v2rayNG.git
synced 2026-11-03 19:54:30 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
007a4e674f | ||
|
|
46af9d803c | ||
|
|
32a9263e0a | ||
|
|
253e171fec | ||
|
|
31b66a705b | ||
|
|
23c7cd5b95 | ||
|
|
32a37dd626 | ||
|
|
53e84d5fd8 | ||
|
|
0519d02001 | ||
|
|
6dfed5bbc2 | ||
|
|
dcd060e350 | ||
|
|
71f94db929 | ||
|
|
d06b9cb999 | ||
|
|
f53a91241c | ||
|
|
3fb067dc99 | ||
|
|
02103287e5 |
No files matched your search
+1
-1
Submodule AndroidLibXrayLite updated: e194d22cf1...85f272f37c.
@@ -3,7 +3,7 @@
|
|||||||
A V2Ray client for Android, support [Xray core](https://github.com/XTLS/Xray-core) and [v2fly core](https://github.com/v2fly/v2ray-core)
|
A V2Ray client for Android, support [Xray core](https://github.com/XTLS/Xray-core) and [v2fly core](https://github.com/v2fly/v2ray-core)
|
||||||
|
|
||||||
[](https://developer.android.com/about/versions/lollipop)
|
[](https://developer.android.com/about/versions/lollipop)
|
||||||
[](https://kotlinlang.org)
|
[](https://kotlinlang.org)
|
||||||
[](https://github.com/2dust/v2rayNG/commits/master)
|
[](https://github.com/2dust/v2rayNG/commits/master)
|
||||||
[](https://www.codefactor.io/repository/github/2dust/v2rayng)
|
[](https://www.codefactor.io/repository/github/2dust/v2rayng)
|
||||||
[](https://github.com/2dust/v2rayNG/releases)
|
[](https://github.com/2dust/v2rayNG/releases)
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ android {
|
|||||||
applicationId = "com.v2ray.ang"
|
applicationId = "com.v2ray.ang"
|
||||||
minSdk = 24
|
minSdk = 24
|
||||||
targetSdk = 37
|
targetSdk = 37
|
||||||
versionCode = 733
|
versionCode = 734
|
||||||
versionName = "2.2.3"
|
versionName = "2.2.4"
|
||||||
multiDexEnabled = true
|
multiDexEnabled = true
|
||||||
|
|
||||||
val abiFilterList = (properties["ABI_FILTERS"] as? String)?.split(';')
|
val abiFilterList = (properties["ABI_FILTERS"] as? String)?.split(';')
|
||||||
@@ -21,7 +21,7 @@ android {
|
|||||||
abi {
|
abi {
|
||||||
isEnable = true
|
isEnable = true
|
||||||
reset()
|
reset()
|
||||||
if (abiFilterList != null && abiFilterList.isNotEmpty()) {
|
if (!abiFilterList.isNullOrEmpty()) {
|
||||||
include(*abiFilterList.toTypedArray())
|
include(*abiFilterList.toTypedArray())
|
||||||
} else {
|
} else {
|
||||||
include(
|
include(
|
||||||
|
|||||||
@@ -203,13 +203,19 @@ object AppConfig {
|
|||||||
const val GOOGLEAPIS_COM_DOMAIN = "googleapis.com"
|
const val GOOGLEAPIS_COM_DOMAIN = "googleapis.com"
|
||||||
|
|
||||||
// Android Private DNS constants
|
// Android Private DNS constants
|
||||||
const val DNS_DNSPOD_DOMAIN = "dot.pub"
|
|
||||||
const val DNS_ALIDNS_DOMAIN = "dns.alidns.com"
|
const val DNS_ALIDNS_DOMAIN = "dns.alidns.com"
|
||||||
|
const val DNS_CISCO_SSE_DOMAIN = "dns.sse.cisco.com"
|
||||||
|
const val DNS_CISCO_UMBRELLA_DOMAIN = "dns.umbrella.com"
|
||||||
const val DNS_CLOUDFLARE_ONE_DOMAIN = "one.one.one.one"
|
const val DNS_CLOUDFLARE_ONE_DOMAIN = "one.one.one.one"
|
||||||
|
const val DNS_CLOUDFLARE_ONEDOT_DNS_DOMAIN = "1dot1dot1dot1.cloudflare-dns.com"
|
||||||
const val DNS_CLOUDFLARE_DNS_COM_DOMAIN = "dns.cloudflare.com"
|
const val DNS_CLOUDFLARE_DNS_COM_DOMAIN = "dns.cloudflare.com"
|
||||||
const val DNS_CLOUDFLARE_DNS_DOMAIN = "cloudflare-dns.com"
|
const val DNS_CLOUDFLARE_DNS_DOMAIN = "cloudflare-dns.com"
|
||||||
|
const val DNS_CLOUDFLARE_WARP_DOMAIN = "engage.cloudflareclient.com"
|
||||||
|
const val DNS_DNSPOD_DOH_DOMAIN = "doh.pub"
|
||||||
|
const val DNS_DNSPOD_DOT_DOMAIN = "dot.pub"
|
||||||
const val DNS_GOOGLE_DOMAIN = "dns.google"
|
const val DNS_GOOGLE_DOMAIN = "dns.google"
|
||||||
const val DNS_QUAD9_DOMAIN = "dns.quad9.net"
|
const val DNS_QUAD9_DOMAIN = "dns.quad9.net"
|
||||||
|
const val DNS_SB_DOMAIN = "dns.sb"
|
||||||
const val DNS_YANDEX_DOMAIN = "common.dot.dns.yandex.net"
|
const val DNS_YANDEX_DOMAIN = "common.dot.dns.yandex.net"
|
||||||
|
|
||||||
const val DEFAULT_PORT = 443
|
const val DEFAULT_PORT = 443
|
||||||
@@ -223,12 +229,18 @@ object AppConfig {
|
|||||||
const val UNIDENTIFIED_PACKAGE = "__unknown_app__"
|
const val UNIDENTIFIED_PACKAGE = "__unknown_app__"
|
||||||
|
|
||||||
val DNS_ALIDNS_ADDRESSES = arrayListOf("223.5.5.5", "223.6.6.6", "2400:3200::1", "2400:3200:baba::1")
|
val DNS_ALIDNS_ADDRESSES = arrayListOf("223.5.5.5", "223.6.6.6", "2400:3200::1", "2400:3200:baba::1")
|
||||||
|
val DNS_CISCO_SSE_ADDRESSES = arrayListOf("208.67.220.220", "208.67.222.222", "2620:119:35::35", "2620:119:53::53")
|
||||||
|
val DNS_CISCO_UMBRELLA_ADDRESSES = arrayListOf("208.67.220.220", "208.67.222.222", "2620:119:35::35", "2620:119:53::53")
|
||||||
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_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_ONEDOT_DNS_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("162.159.61.8", "172.64.41.8", "2a06:98c1:52::8", "2803:f800:53::8")
|
||||||
val DNS_CLOUDFLARE_DNS_ADDRESSES = arrayListOf("104.16.248.249", "104.16.249.249", "2606:4700::6810:f8f9", "2606:4700::6810:f9f9")
|
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_CLOUDFLARE_WARP_ADDRESSES = arrayListOf("162.159.192.1", "2606:4700:d0::a29f:c001")
|
||||||
|
val DNS_DNSPOD_DOH_ADDRESSES = arrayListOf("1.12.12.12", "120.53.53.53")
|
||||||
|
val DNS_DNSPOD_DOT_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_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_QUAD9_ADDRESSES = arrayListOf("9.9.9.9", "149.112.112.112", "2620:fe::fe", "2620:fe::9")
|
||||||
|
val DNS_SB_ADDRESSES = arrayListOf("45.11.45.11", "185.222.222.222", "2a09::", "2a11::")
|
||||||
val DNS_YANDEX_ADDRESSES = arrayListOf("77.88.8.8", "77.88.8.1", "2a02:6b8::feed:0ff", "2a02:6b8:0:1::feed:0ff")
|
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
|
//minimum list https://serverfault.com/a/304791
|
||||||
|
|||||||
@@ -710,12 +710,18 @@ object CoreConfigManager {
|
|||||||
|
|
||||||
// hardcode popular Android Private DNS rule to fix localhost DNS problem
|
// hardcode popular Android Private DNS rule to fix localhost DNS problem
|
||||||
hosts[AppConfig.DNS_ALIDNS_DOMAIN] = AppConfig.DNS_ALIDNS_ADDRESSES
|
hosts[AppConfig.DNS_ALIDNS_DOMAIN] = AppConfig.DNS_ALIDNS_ADDRESSES
|
||||||
|
hosts[AppConfig.DNS_CISCO_SSE_DOMAIN] = AppConfig.DNS_CISCO_SSE_ADDRESSES
|
||||||
|
hosts[AppConfig.DNS_CISCO_UMBRELLA_DOMAIN] = AppConfig.DNS_CISCO_UMBRELLA_ADDRESSES
|
||||||
hosts[AppConfig.DNS_CLOUDFLARE_ONE_DOMAIN] = AppConfig.DNS_CLOUDFLARE_ONE_ADDRESSES
|
hosts[AppConfig.DNS_CLOUDFLARE_ONE_DOMAIN] = AppConfig.DNS_CLOUDFLARE_ONE_ADDRESSES
|
||||||
|
hosts[AppConfig.DNS_CLOUDFLARE_ONEDOT_DNS_DOMAIN] = AppConfig.DNS_CLOUDFLARE_ONEDOT_DNS_ADDRESSES
|
||||||
hosts[AppConfig.DNS_CLOUDFLARE_DNS_COM_DOMAIN] = AppConfig.DNS_CLOUDFLARE_DNS_COM_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_CLOUDFLARE_DNS_DOMAIN] = AppConfig.DNS_CLOUDFLARE_DNS_ADDRESSES
|
||||||
hosts[AppConfig.DNS_DNSPOD_DOMAIN] = AppConfig.DNS_DNSPOD_ADDRESSES
|
hosts[AppConfig.DNS_CLOUDFLARE_WARP_DOMAIN] = AppConfig.DNS_CLOUDFLARE_WARP_ADDRESSES
|
||||||
|
hosts[AppConfig.DNS_DNSPOD_DOH_DOMAIN] = AppConfig.DNS_DNSPOD_DOH_ADDRESSES
|
||||||
|
hosts[AppConfig.DNS_DNSPOD_DOT_DOMAIN] = AppConfig.DNS_DNSPOD_DOT_ADDRESSES
|
||||||
hosts[AppConfig.DNS_GOOGLE_DOMAIN] = AppConfig.DNS_GOOGLE_ADDRESSES
|
hosts[AppConfig.DNS_GOOGLE_DOMAIN] = AppConfig.DNS_GOOGLE_ADDRESSES
|
||||||
hosts[AppConfig.DNS_QUAD9_DOMAIN] = AppConfig.DNS_QUAD9_ADDRESSES
|
hosts[AppConfig.DNS_QUAD9_DOMAIN] = AppConfig.DNS_QUAD9_ADDRESSES
|
||||||
|
hosts[AppConfig.DNS_SB_DOMAIN] = AppConfig.DNS_SB_ADDRESSES
|
||||||
hosts[AppConfig.DNS_YANDEX_DOMAIN] = AppConfig.DNS_YANDEX_ADDRESSES
|
hosts[AppConfig.DNS_YANDEX_DOMAIN] = AppConfig.DNS_YANDEX_ADDRESSES
|
||||||
|
|
||||||
//User DNS hosts
|
//User DNS hosts
|
||||||
|
|||||||
@@ -564,6 +564,7 @@ object CoreOutboundBuilder {
|
|||||||
fingerprint = profileItem.fingerPrint.nullIfBlank(),
|
fingerprint = profileItem.fingerPrint.nullIfBlank(),
|
||||||
alpn = profileItem.alpn?.split(",")?.map { it.trim() }?.filter { it.isNotEmpty() }.takeIf { !it.isNullOrEmpty() },
|
alpn = profileItem.alpn?.split(",")?.map { it.trim() }?.filter { it.isNotEmpty() }.takeIf { !it.isNullOrEmpty() },
|
||||||
echConfigList = profileItem.echConfigList.nullIfBlank(),
|
echConfigList = profileItem.echConfigList.nullIfBlank(),
|
||||||
|
verifyPeerCertByName = profileItem.verifyPeerCertByName.nullIfBlank(),
|
||||||
pinnedPeerCertSha256 = profileItem.pinnedCA256.nullIfBlank(),
|
pinnedPeerCertSha256 = profileItem.pinnedCA256.nullIfBlank(),
|
||||||
publicKey = profileItem.publicKey.nullIfBlank(),
|
publicKey = profileItem.publicKey.nullIfBlank(),
|
||||||
shortId = profileItem.shortId.nullIfBlank(),
|
shortId = profileItem.shortId.nullIfBlank(),
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package com.v2ray.ang.dto
|
||||||
|
|
||||||
|
data class CertSha256Request(
|
||||||
|
val address: String = "",
|
||||||
|
val port: Int = 443,
|
||||||
|
val serverName: String? = null,
|
||||||
|
val timeoutMs: Long = 5000L,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class CertSha256Result(
|
||||||
|
val sha256: String = "",
|
||||||
|
val error: String = "",
|
||||||
|
)
|
||||||
@@ -253,6 +253,7 @@ data class V2rayConfig(
|
|||||||
val disableSystemRoot: Boolean? = null,
|
val disableSystemRoot: Boolean? = null,
|
||||||
val enableSessionResumption: Boolean? = null,
|
val enableSessionResumption: Boolean? = null,
|
||||||
var echConfigList: String? = null,
|
var echConfigList: String? = null,
|
||||||
|
var verifyPeerCertByName: String? = null,
|
||||||
var pinnedPeerCertSha256: String? = null,
|
var pinnedPeerCertSha256: String? = null,
|
||||||
// REALITY settings
|
// REALITY settings
|
||||||
val show: Boolean = false,
|
val show: Boolean = false,
|
||||||
|
|||||||
@@ -16,5 +16,7 @@ data class VmessQRCode(
|
|||||||
var sni: String = "",
|
var sni: String = "",
|
||||||
var alpn: String = "",
|
var alpn: String = "",
|
||||||
var fp: String = "",
|
var fp: String = "",
|
||||||
var insecure: String = ""
|
var insecure: String = "",
|
||||||
|
var vcn: String = "",
|
||||||
|
var pcs: String = ""
|
||||||
)
|
)
|
||||||
@@ -42,6 +42,7 @@ data class ProfileItem(
|
|||||||
var fingerPrint: String? = null,
|
var fingerPrint: String? = null,
|
||||||
var insecure: Boolean? = null,
|
var insecure: Boolean? = null,
|
||||||
var echConfigList: String? = null,
|
var echConfigList: String? = null,
|
||||||
|
var verifyPeerCertByName: String? = null,
|
||||||
var pinnedCA256: String? = null,
|
var pinnedCA256: String? = null,
|
||||||
|
|
||||||
var publicKey: String? = null,
|
var publicKey: String? = null,
|
||||||
|
|||||||
@@ -84,6 +84,7 @@ open class FmtBase {
|
|||||||
config.fingerPrint = queryParam["fp"]
|
config.fingerPrint = queryParam["fp"]
|
||||||
config.alpn = queryParam["alpn"]
|
config.alpn = queryParam["alpn"]
|
||||||
config.echConfigList = queryParam["ech"]
|
config.echConfigList = queryParam["ech"]
|
||||||
|
config.verifyPeerCertByName = queryParam["vcn"]
|
||||||
config.pinnedCA256 = queryParam["pcs"]
|
config.pinnedCA256 = queryParam["pcs"]
|
||||||
config.publicKey = queryParam["pbk"]
|
config.publicKey = queryParam["pbk"]
|
||||||
config.shortId = queryParam["sid"]
|
config.shortId = queryParam["sid"]
|
||||||
@@ -104,6 +105,7 @@ open class FmtBase {
|
|||||||
config.sni?.nullIfBlank()?.let { dicQuery["sni"] = it }
|
config.sni?.nullIfBlank()?.let { dicQuery["sni"] = it }
|
||||||
config.alpn?.nullIfBlank()?.let { dicQuery["alpn"] = it }
|
config.alpn?.nullIfBlank()?.let { dicQuery["alpn"] = it }
|
||||||
config.echConfigList?.nullIfBlank()?.let { dicQuery["ech"] = it }
|
config.echConfigList?.nullIfBlank()?.let { dicQuery["ech"] = it }
|
||||||
|
config.verifyPeerCertByName?.nullIfBlank()?.let { dicQuery["vcn"] = it }
|
||||||
config.pinnedCA256?.nullIfBlank()?.let { dicQuery["pcs"] = it }
|
config.pinnedCA256?.nullIfBlank()?.let { dicQuery["pcs"] = it }
|
||||||
config.fingerPrint?.nullIfBlank()?.let { dicQuery["fp"] = it }
|
config.fingerPrint?.nullIfBlank()?.let { dicQuery["fp"] = it }
|
||||||
config.publicKey?.nullIfBlank()?.let { dicQuery["pbk"] = it }
|
config.publicKey?.nullIfBlank()?.let { dicQuery["pbk"] = it }
|
||||||
|
|||||||
@@ -88,6 +88,9 @@ object VmessFmt : FmtBase() {
|
|||||||
"0" -> false
|
"0" -> false
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
|
config.verifyPeerCertByName = vmessQRCode.vcn
|
||||||
|
config.pinnedCA256 = vmessQRCode.pcs
|
||||||
|
|
||||||
return config
|
return config
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,6 +144,8 @@ object VmessFmt : FmtBase() {
|
|||||||
false -> "0"
|
false -> "0"
|
||||||
else -> ""
|
else -> ""
|
||||||
}
|
}
|
||||||
|
vmessQRCode.vcn = config.verifyPeerCertByName.orEmpty()
|
||||||
|
vmessQRCode.pcs = config.pinnedCA256.orEmpty()
|
||||||
|
|
||||||
val json = JsonUtil.toJson(vmessQRCode)
|
val json = JsonUtil.toJson(vmessQRCode)
|
||||||
return Utils.encode(json)
|
return Utils.encode(json)
|
||||||
|
|||||||
@@ -0,0 +1,79 @@
|
|||||||
|
package com.v2ray.ang.handler
|
||||||
|
|
||||||
|
import com.v2ray.ang.AppConfig
|
||||||
|
import com.v2ray.ang.dto.CertSha256Request
|
||||||
|
import com.v2ray.ang.dto.CertSha256Result
|
||||||
|
import com.v2ray.ang.dto.entities.ProfileItem
|
||||||
|
import com.v2ray.ang.enums.EConfigType
|
||||||
|
import com.v2ray.ang.util.HttpUtil
|
||||||
|
import com.v2ray.ang.util.JsonUtil
|
||||||
|
import com.v2ray.ang.util.LogUtil
|
||||||
|
import com.v2ray.ang.util.Utils
|
||||||
|
import libv2ray.Libv2ray
|
||||||
|
|
||||||
|
object CertificateFingerprintManager {
|
||||||
|
private const val TIMEOUT_MS = 5000L
|
||||||
|
|
||||||
|
fun fetchForManualFill(profile: ProfileItem): String? {
|
||||||
|
val request = buildRequest(profile) ?: return null
|
||||||
|
val result = if (profile.configType == EConfigType.HYSTERIA2) {
|
||||||
|
fetch("quic", request) { Libv2ray.fetchQuicCertSha256(it) }
|
||||||
|
} else {
|
||||||
|
fetch("tls", request) { Libv2ray.fetchTlsCertSha256(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
?.takeIf { it.error.isBlank() }
|
||||||
|
?.sha256
|
||||||
|
?.takeIf { it.isNotBlank() }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun buildRequest(profile: ProfileItem): CertSha256Request? {
|
||||||
|
if (!isFetchable(profile)) return null
|
||||||
|
|
||||||
|
val server = profile.server?.trim()?.takeIf { it.isNotEmpty() } ?: return null
|
||||||
|
val port = profile.serverPort?.toIntOrNull()?.takeIf { it > 0 } ?: AppConfig.DEFAULT_PORT
|
||||||
|
|
||||||
|
return CertSha256Request(
|
||||||
|
address = resolveDialAddress(server),
|
||||||
|
port = port,
|
||||||
|
serverName = inferServerName(profile),
|
||||||
|
timeoutMs = TIMEOUT_MS,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isFetchable(profile: ProfileItem): Boolean {
|
||||||
|
return profile.configType == EConfigType.HYSTERIA2 || profile.security == AppConfig.TLS
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun fetch(
|
||||||
|
type: String,
|
||||||
|
request: CertSha256Request,
|
||||||
|
fetcher: (String) -> String,
|
||||||
|
): CertSha256Result? {
|
||||||
|
return try {
|
||||||
|
JsonUtil.fromJsonSafe(fetcher(JsonUtil.toJson(request)), CertSha256Result::class.java)
|
||||||
|
} catch (e: UnsatisfiedLinkError) {
|
||||||
|
LogUtil.e(AppConfig.TAG, "Fetch $type cert SHA-256 API missing in libv2ray", e)
|
||||||
|
null
|
||||||
|
} catch (e: Exception) {
|
||||||
|
LogUtil.e(AppConfig.TAG, "Fetch $type cert SHA-256 failed", e)
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun resolveDialAddress(server: String): String {
|
||||||
|
if (Utils.isPureIpAddress(server) || !Utils.isDomainName(server)) return server
|
||||||
|
|
||||||
|
val preferIpv6 = MmkvManager.decodeSettingsBool(AppConfig.PREF_PREFER_IPV6, false)
|
||||||
|
return HttpUtil.resolveHostToIP(server, preferIpv6)
|
||||||
|
?.firstOrNull()
|
||||||
|
?.takeIf { it.isNotBlank() }
|
||||||
|
?: server
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun inferServerName(profile: ProfileItem): String? {
|
||||||
|
val sni = profile.sni?.takeIf { it.isNotBlank() }
|
||||||
|
return sni?.takeUnless { Utils.isPureIpAddress(it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -226,6 +226,7 @@ class GroupServerFragment : BaseFragment<FragmentGroupServerBinding>(),
|
|||||||
private fun removeServerSub(guid: String, position: Int) {
|
private fun removeServerSub(guid: String, position: Int) {
|
||||||
mainViewModel.removeServer(guid)
|
mainViewModel.removeServer(guid)
|
||||||
adapter.removeServerSub(guid, position)
|
adapter.removeServerSub(guid, position)
|
||||||
|
ownerActivity.refreshGroupTabTitles()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -76,23 +76,7 @@ class MainActivity : HelperBaseActivity(), NavigationView.OnNavigationItemSelect
|
|||||||
binding.viewPager.isUserInputEnabled = true
|
binding.viewPager.isUserInputEnabled = true
|
||||||
|
|
||||||
// setup navigation drawer
|
// setup navigation drawer
|
||||||
val toggle = ActionBarDrawerToggle(
|
setupNavigationDrawer()
|
||||||
this, binding.drawerLayout, binding.toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close
|
|
||||||
)
|
|
||||||
binding.drawerLayout.addDrawerListener(toggle)
|
|
||||||
toggle.syncState()
|
|
||||||
binding.navView.setNavigationItemSelectedListener(this)
|
|
||||||
onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
|
|
||||||
override fun handleOnBackPressed() {
|
|
||||||
if (binding.drawerLayout.isDrawerOpen(GravityCompat.START)) {
|
|
||||||
binding.drawerLayout.closeDrawer(GravityCompat.START)
|
|
||||||
} else {
|
|
||||||
isEnabled = false
|
|
||||||
onBackPressedDispatcher.onBackPressed()
|
|
||||||
isEnabled = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
binding.fab.setOnClickListener { handleFabAction() }
|
binding.fab.setOnClickListener { handleFabAction() }
|
||||||
binding.layoutTest.setOnClickListener { handleLayoutTestClick() }
|
binding.layoutTest.setOnClickListener { handleLayoutTestClick() }
|
||||||
@@ -106,6 +90,31 @@ class MainActivity : HelperBaseActivity(), NavigationView.OnNavigationItemSelect
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun setupNavigationDrawer() {
|
||||||
|
val toggle = ActionBarDrawerToggle(
|
||||||
|
this,
|
||||||
|
binding.drawerLayout,
|
||||||
|
binding.toolbar,
|
||||||
|
R.string.navigation_drawer_open,
|
||||||
|
R.string.navigation_drawer_close
|
||||||
|
)
|
||||||
|
binding.drawerLayout.addDrawerListener(toggle)
|
||||||
|
toggle.syncState()
|
||||||
|
binding.navView.setNavigationItemSelectedListener(this)
|
||||||
|
|
||||||
|
onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
|
||||||
|
override fun handleOnBackPressed() {
|
||||||
|
if (binding.drawerLayout.isDrawerOpen(GravityCompat.START)) {
|
||||||
|
binding.drawerLayout.closeDrawer(GravityCompat.START)
|
||||||
|
} else {
|
||||||
|
isEnabled = false
|
||||||
|
onBackPressedDispatcher.onBackPressed()
|
||||||
|
isEnabled = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
private fun setupViewModel() {
|
private fun setupViewModel() {
|
||||||
mainViewModel.updateTestResultAction.observe(this) { setTestState(it) }
|
mainViewModel.updateTestResultAction.observe(this) { setTestState(it) }
|
||||||
mainViewModel.isRunning.observe(this) { isRunning ->
|
mainViewModel.isRunning.observe(this) { isRunning ->
|
||||||
@@ -131,6 +140,26 @@ class MainActivity : HelperBaseActivity(), NavigationView.OnNavigationItemSelect
|
|||||||
binding.viewPager.setCurrentItem(targetIndex, false)
|
binding.viewPager.setCurrentItem(targetIndex, false)
|
||||||
|
|
||||||
binding.tabGroup.isVisible = groups.size > 1
|
binding.tabGroup.isVisible = groups.size > 1
|
||||||
|
refreshGroupTabTitles(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun refreshGroupTabTitles(refreshAll: Boolean = false) {
|
||||||
|
val groupsToRefresh = if (refreshAll || mainViewModel.subscriptionId.isEmpty()) {
|
||||||
|
groupPagerAdapter.groups
|
||||||
|
} else {
|
||||||
|
groupPagerAdapter.groups.filter { it.id == mainViewModel.subscriptionId }
|
||||||
|
}
|
||||||
|
|
||||||
|
groupsToRefresh.forEach { group ->
|
||||||
|
if (group.id.isEmpty()) {
|
||||||
|
return@forEach
|
||||||
|
}
|
||||||
|
val tabIndex = groupPagerAdapter.groups.indexOfFirst { it.id == group.id }
|
||||||
|
if (tabIndex >= 0) {
|
||||||
|
val count = MmkvManager.decodeServerList(group.id).size
|
||||||
|
binding.tabGroup.getTabAt(tabIndex)?.text = "${group.remarks} ($count)"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleFabAction() {
|
private fun handleFabAction() {
|
||||||
@@ -416,6 +445,7 @@ class MainActivity : HelperBaseActivity(), NavigationView.OnNavigationItemSelect
|
|||||||
count > 0 -> {
|
count > 0 -> {
|
||||||
toast(getString(R.string.title_import_config_count, count))
|
toast(getString(R.string.title_import_config_count, count))
|
||||||
mainViewModel.reloadServerList()
|
mainViewModel.reloadServerList()
|
||||||
|
refreshGroupTabTitles()
|
||||||
}
|
}
|
||||||
|
|
||||||
countSub > 0 -> setupGroupTab()
|
countSub > 0 -> setupGroupTab()
|
||||||
@@ -471,6 +501,7 @@ class MainActivity : HelperBaseActivity(), NavigationView.OnNavigationItemSelect
|
|||||||
}
|
}
|
||||||
if (result.configCount > 0) {
|
if (result.configCount > 0) {
|
||||||
mainViewModel.reloadServerList()
|
mainViewModel.reloadServerList()
|
||||||
|
refreshGroupTabTitles()
|
||||||
}
|
}
|
||||||
hideLoading()
|
hideLoading()
|
||||||
}
|
}
|
||||||
@@ -500,6 +531,7 @@ class MainActivity : HelperBaseActivity(), NavigationView.OnNavigationItemSelect
|
|||||||
val ret = mainViewModel.removeAllServer()
|
val ret = mainViewModel.removeAllServer()
|
||||||
launch(Dispatchers.Main) {
|
launch(Dispatchers.Main) {
|
||||||
mainViewModel.reloadServerList()
|
mainViewModel.reloadServerList()
|
||||||
|
refreshGroupTabTitles()
|
||||||
toast(getString(R.string.title_del_config_count, ret))
|
toast(getString(R.string.title_del_config_count, ret))
|
||||||
hideLoading()
|
hideLoading()
|
||||||
}
|
}
|
||||||
@@ -519,6 +551,7 @@ class MainActivity : HelperBaseActivity(), NavigationView.OnNavigationItemSelect
|
|||||||
val ret = mainViewModel.removeDuplicateServer()
|
val ret = mainViewModel.removeDuplicateServer()
|
||||||
launch(Dispatchers.Main) {
|
launch(Dispatchers.Main) {
|
||||||
mainViewModel.reloadServerList()
|
mainViewModel.reloadServerList()
|
||||||
|
refreshGroupTabTitles()
|
||||||
toast(getString(R.string.title_del_duplicate_config_count, ret))
|
toast(getString(R.string.title_del_duplicate_config_count, ret))
|
||||||
hideLoading()
|
hideLoading()
|
||||||
}
|
}
|
||||||
@@ -538,6 +571,7 @@ class MainActivity : HelperBaseActivity(), NavigationView.OnNavigationItemSelect
|
|||||||
val ret = mainViewModel.removeInvalidServer()
|
val ret = mainViewModel.removeInvalidServer()
|
||||||
launch(Dispatchers.Main) {
|
launch(Dispatchers.Main) {
|
||||||
mainViewModel.reloadServerList()
|
mainViewModel.reloadServerList()
|
||||||
|
refreshGroupTabTitles()
|
||||||
toast(getString(R.string.title_del_config_count, ret))
|
toast(getString(R.string.title_del_config_count, ret))
|
||||||
hideLoading()
|
hideLoading()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -159,8 +159,12 @@ class MainRecyclerAdapter(
|
|||||||
|
|
||||||
// Security: hide blank or tls
|
// Security: hide blank or tls
|
||||||
profile.security?.let { sec ->
|
profile.security?.let { sec ->
|
||||||
if (sec.isNotBlank() && !sec.equals("tls", ignoreCase = true)) {
|
if (sec.isNotBlank()) {
|
||||||
parts.add(sec)
|
if (profile.insecure == true && sec.equals("tls", ignoreCase = true)) {
|
||||||
|
parts.add("$sec insecure") // TODO
|
||||||
|
} else {
|
||||||
|
parts.add(sec)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,11 +7,13 @@ import android.view.MenuItem
|
|||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.AdapterView
|
import android.widget.AdapterView
|
||||||
import android.widget.ArrayAdapter
|
import android.widget.ArrayAdapter
|
||||||
|
import android.widget.Button
|
||||||
import android.widget.EditText
|
import android.widget.EditText
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
import android.widget.Spinner
|
import android.widget.Spinner
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.v2ray.ang.AppConfig
|
import com.v2ray.ang.AppConfig
|
||||||
import com.v2ray.ang.AppConfig.DEFAULT_PORT
|
import com.v2ray.ang.AppConfig.DEFAULT_PORT
|
||||||
import com.v2ray.ang.AppConfig.REALITY
|
import com.v2ray.ang.AppConfig.REALITY
|
||||||
@@ -27,10 +29,14 @@ import com.v2ray.ang.extension.nullIfBlank
|
|||||||
import com.v2ray.ang.extension.toast
|
import com.v2ray.ang.extension.toast
|
||||||
import com.v2ray.ang.extension.toastSuccess
|
import com.v2ray.ang.extension.toastSuccess
|
||||||
import com.v2ray.ang.handler.AngConfigManager
|
import com.v2ray.ang.handler.AngConfigManager
|
||||||
|
import com.v2ray.ang.handler.CertificateFingerprintManager
|
||||||
import com.v2ray.ang.handler.MmkvManager
|
import com.v2ray.ang.handler.MmkvManager
|
||||||
import com.v2ray.ang.handler.SettingsChangeManager
|
import com.v2ray.ang.handler.SettingsChangeManager
|
||||||
import com.v2ray.ang.util.JsonUtil
|
import com.v2ray.ang.util.JsonUtil
|
||||||
import com.v2ray.ang.util.Utils
|
import com.v2ray.ang.util.Utils
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
class ServerActivity : BaseActivity() {
|
class ServerActivity : BaseActivity() {
|
||||||
|
|
||||||
@@ -140,7 +146,10 @@ class ServerActivity : BaseActivity() {
|
|||||||
private val layout_extra: LinearLayout? by lazy { findViewById(R.id.layout_extra) }
|
private val layout_extra: LinearLayout? by lazy { findViewById(R.id.layout_extra) }
|
||||||
private val et_ech_config_list: EditText? by lazy { findViewById(R.id.et_ech_config_list) }
|
private val et_ech_config_list: EditText? by lazy { findViewById(R.id.et_ech_config_list) }
|
||||||
private val container_ech_config_list: LinearLayout? by lazy { findViewById(R.id.lay_ech_config_list) }
|
private val container_ech_config_list: LinearLayout? by lazy { findViewById(R.id.lay_ech_config_list) }
|
||||||
|
private val et_verify_peer_cert_by_name: EditText? by lazy { findViewById(R.id.et_verify_peer_cert_by_name) }
|
||||||
|
private val container_verify_peer_cert_by_name: LinearLayout? by lazy { findViewById(R.id.lay_verify_peer_cert_by_name) }
|
||||||
private val et_pinned_ca256: EditText? by lazy { findViewById(R.id.et_pinned_ca256) }
|
private val et_pinned_ca256: EditText? by lazy { findViewById(R.id.et_pinned_ca256) }
|
||||||
|
private val btn_pinned_ca256_action: Button? by lazy { findViewById(R.id.btn_pinned_ca256_action) }
|
||||||
private val container_pinned_ca256: LinearLayout? by lazy { findViewById(R.id.lay_pinned_ca256) }
|
private val container_pinned_ca256: LinearLayout? by lazy { findViewById(R.id.lay_pinned_ca256) }
|
||||||
private val layout_browser_dialer: LinearLayout? by lazy { findViewById(R.id.layout_browser_dialer) }
|
private val layout_browser_dialer: LinearLayout? by lazy { findViewById(R.id.layout_browser_dialer) }
|
||||||
private val sp_browser_dialer_mode: Spinner? by lazy { findViewById(R.id.sp_browser_dialer_mode) }
|
private val sp_browser_dialer_mode: Spinner? by lazy { findViewById(R.id.sp_browser_dialer_mode) }
|
||||||
@@ -295,6 +304,7 @@ class ServerActivity : BaseActivity() {
|
|||||||
container_spider_x,
|
container_spider_x,
|
||||||
container_mldsa65_verify,
|
container_mldsa65_verify,
|
||||||
container_ech_config_list,
|
container_ech_config_list,
|
||||||
|
container_verify_peer_cert_by_name,
|
||||||
container_pinned_ca256
|
container_pinned_ca256
|
||||||
).forEach { it?.visibility = View.GONE }
|
).forEach { it?.visibility = View.GONE }
|
||||||
}
|
}
|
||||||
@@ -307,6 +317,7 @@ class ServerActivity : BaseActivity() {
|
|||||||
container_alpn,
|
container_alpn,
|
||||||
container_allow_insecure,
|
container_allow_insecure,
|
||||||
container_ech_config_list,
|
container_ech_config_list,
|
||||||
|
container_verify_peer_cert_by_name,
|
||||||
container_pinned_ca256
|
container_pinned_ca256
|
||||||
).forEach { it?.visibility = View.VISIBLE }
|
).forEach { it?.visibility = View.VISIBLE }
|
||||||
listOf(
|
listOf(
|
||||||
@@ -327,6 +338,7 @@ class ServerActivity : BaseActivity() {
|
|||||||
container_alpn,
|
container_alpn,
|
||||||
container_allow_insecure,
|
container_allow_insecure,
|
||||||
container_ech_config_list,
|
container_ech_config_list,
|
||||||
|
container_verify_peer_cert_by_name,
|
||||||
container_pinned_ca256
|
container_pinned_ca256
|
||||||
).forEach { it?.visibility = View.GONE }
|
).forEach { it?.visibility = View.GONE }
|
||||||
listOf(
|
listOf(
|
||||||
@@ -343,6 +355,9 @@ class ServerActivity : BaseActivity() {
|
|||||||
// do nothing
|
// do nothing
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
btn_pinned_ca256_action?.setOnClickListener {
|
||||||
|
fetchPinnedCA256ForCurrentConfig()
|
||||||
|
}
|
||||||
if (config != null) {
|
if (config != null) {
|
||||||
bindingServer(config)
|
bindingServer(config)
|
||||||
} else {
|
} else {
|
||||||
@@ -410,6 +425,7 @@ class ServerActivity : BaseActivity() {
|
|||||||
sp_allow_insecure?.setSelection(allowinsecure)
|
sp_allow_insecure?.setSelection(allowinsecure)
|
||||||
}
|
}
|
||||||
et_ech_config_list?.text = Utils.getEditable(config.echConfigList)
|
et_ech_config_list?.text = Utils.getEditable(config.echConfigList)
|
||||||
|
et_verify_peer_cert_by_name?.text = Utils.getEditable(config.verifyPeerCertByName)
|
||||||
et_pinned_ca256?.text = Utils.getEditable(config.pinnedCA256)
|
et_pinned_ca256?.text = Utils.getEditable(config.pinnedCA256)
|
||||||
} else if (config.security == REALITY) {
|
} else if (config.security == REALITY) {
|
||||||
et_public_key?.text = Utils.getEditable(config.publicKey.orEmpty())
|
et_public_key?.text = Utils.getEditable(config.publicKey.orEmpty())
|
||||||
@@ -613,6 +629,7 @@ class ServerActivity : BaseActivity() {
|
|||||||
val spiderX = et_spider_x?.text?.toString()
|
val spiderX = et_spider_x?.text?.toString()
|
||||||
val mldsa65Verify = et_mldsa65_verify?.text?.toString()
|
val mldsa65Verify = et_mldsa65_verify?.text?.toString()
|
||||||
val echConfigList = et_ech_config_list?.text?.toString()
|
val echConfigList = et_ech_config_list?.text?.toString()
|
||||||
|
val verifyPeerCertByName = et_verify_peer_cert_by_name?.text?.toString()
|
||||||
val pinnedCA256 = et_pinned_ca256?.text?.toString()
|
val pinnedCA256 = et_pinned_ca256?.text?.toString()
|
||||||
|
|
||||||
val allowInsecure =
|
val allowInsecure =
|
||||||
@@ -632,9 +649,51 @@ class ServerActivity : BaseActivity() {
|
|||||||
config.spiderX = spiderX
|
config.spiderX = spiderX
|
||||||
config.mldsa65Verify = mldsa65Verify
|
config.mldsa65Verify = mldsa65Verify
|
||||||
config.echConfigList = echConfigList
|
config.echConfigList = echConfigList
|
||||||
|
config.verifyPeerCertByName = verifyPeerCertByName
|
||||||
config.pinnedCA256 = pinnedCA256
|
config.pinnedCA256 = pinnedCA256
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun fetchPinnedCA256ForCurrentConfig() {
|
||||||
|
val config = buildCurrentProfileForCertificateFetch() ?: return
|
||||||
|
|
||||||
|
lifecycleScope.launch {
|
||||||
|
btn_pinned_ca256_action?.isEnabled = false
|
||||||
|
try {
|
||||||
|
val sha256 = withContext(Dispatchers.IO) {
|
||||||
|
CertificateFingerprintManager.fetchForManualFill(config)
|
||||||
|
}
|
||||||
|
if (sha256.isNullOrBlank()) {
|
||||||
|
toast(R.string.toast_fetch_cert_sha256_failed)
|
||||||
|
} else {
|
||||||
|
et_pinned_ca256?.text = Utils.getEditable(sha256)
|
||||||
|
toastSuccess(R.string.toast_fetch_cert_sha256_success)
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
btn_pinned_ca256_action?.isEnabled = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun buildCurrentProfileForCertificateFetch(): ProfileItem? {
|
||||||
|
if (TextUtils.isEmpty(et_address.text.toString())) {
|
||||||
|
toast(R.string.server_lab_address)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
val configType = MmkvManager.decodeServerConfig(editGuid)?.configType ?: createConfigType
|
||||||
|
if (configType != EConfigType.HYSTERIA2 && Utils.parseInt(et_port.text.toString()) <= 0) {
|
||||||
|
toast(R.string.server_lab_port)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
val config = ProfileItem.create(configType)
|
||||||
|
saveCommon(config)
|
||||||
|
saveStreamSettings(config)
|
||||||
|
saveTls(config)
|
||||||
|
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
private fun transportTypes(network: String?): Array<out String> {
|
private fun transportTypes(network: String?): Array<out String> {
|
||||||
return when (network) {
|
return when (network) {
|
||||||
NetworkType.TCP.type -> {
|
NetworkType.TCP.type -> {
|
||||||
|
|||||||
@@ -341,11 +341,11 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
if (subscriptionId.isEmpty() && keywordFilter.isEmpty()) {
|
if (subscriptionId.isEmpty() && keywordFilter.isEmpty()) {
|
||||||
MmkvManager.removeAllServer()
|
MmkvManager.removeAllServer()
|
||||||
} else {
|
} else {
|
||||||
val serversCopy = serversCache.toList()
|
val serverList = MmkvManager.decodeServerList(subscriptionId)
|
||||||
for (item in serversCopy) {
|
for (guid in serverList) {
|
||||||
MmkvManager.removeServer(item.guid)
|
MmkvManager.removeServer(guid)
|
||||||
}
|
}
|
||||||
serversCache.toList().count()
|
serverList.count()
|
||||||
}
|
}
|
||||||
return count
|
return count
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -114,7 +114,7 @@
|
|||||||
android:layout_marginTop="@dimen/padding_spacing_dp8"
|
android:layout_marginTop="@dimen/padding_spacing_dp8"
|
||||||
android:layout_marginBottom="@dimen/padding_spacing_dp8"
|
android:layout_marginBottom="@dimen/padding_spacing_dp8"
|
||||||
android:entries="@array/allowinsecures"
|
android:entries="@array/allowinsecures"
|
||||||
android:nextFocusDown="@+id/et_ech_config_list"/>
|
android:nextFocusDown="@+id/et_verify_peer_cert_by_name"/>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
@@ -138,6 +138,27 @@
|
|||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/lay_verify_peer_cert_by_name"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="@dimen/padding_spacing_dp16"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/server_lab_verify_peer_cert_by_name" />
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/et_verify_peer_cert_by_name"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:inputType="text"
|
||||||
|
android:nextFocusDown="@+id/et_ech_config_list" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/lay_pinned_ca256"
|
android:id="@+id/lay_pinned_ca256"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@@ -157,6 +178,14 @@
|
|||||||
android:inputType="text"
|
android:inputType="text"
|
||||||
android:nextFocusDown="@+id/et_public_key" />
|
android:nextFocusDown="@+id/et_public_key" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btn_pinned_ca256_action"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="@dimen/padding_spacing_dp8"
|
||||||
|
android:layout_marginBottom="@dimen/padding_spacing_dp8"
|
||||||
|
android:text="@string/pinned_ca256_action_fetch" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
|
|||||||
@@ -88,6 +88,14 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:inputType="text" />
|
android:inputType="text" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btn_pinned_ca256_action"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="@dimen/padding_spacing_dp8"
|
||||||
|
android:layout_marginBottom="@dimen/padding_spacing_dp8"
|
||||||
|
android:text="@string/pinned_ca256_action_fetch" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
|||||||
@@ -118,7 +118,11 @@
|
|||||||
<string name="server_lab_xhttp_extra">XHTTP Extra raw JSON, format: { XHTTPObject }</string>
|
<string name="server_lab_xhttp_extra">XHTTP Extra raw JSON, format: { XHTTPObject }</string>
|
||||||
<string name="server_lab_final_mask">finalMask raw JSON, format: { FinalMaskObject }</string>
|
<string name="server_lab_final_mask">finalMask raw JSON, format: { FinalMaskObject }</string>
|
||||||
<string name="server_lab_ech_config_list">EchConfigList</string>
|
<string name="server_lab_ech_config_list">EchConfigList</string>
|
||||||
|
<string name="server_lab_verify_peer_cert_by_name">Verify Peer Cert By Name</string>
|
||||||
<string name="server_lab_pinned_ca256">Certificate fingerprint (SHA-256)</string>
|
<string name="server_lab_pinned_ca256">Certificate fingerprint (SHA-256)</string>
|
||||||
|
<string name="pinned_ca256_action_fetch">Fetch and fill certificate fingerprint</string>
|
||||||
|
<string name="toast_fetch_cert_sha256_success">Certificate fingerprint fetched successfully</string>
|
||||||
|
<string name="toast_fetch_cert_sha256_failed">Failed to fetch certificate fingerprint</string>
|
||||||
|
|
||||||
<!-- UserAssetActivity -->
|
<!-- UserAssetActivity -->
|
||||||
<string name="toast_asset_copy_failed">فشل نسخ الملف، يرجى استخدام مدير الملفات</string>
|
<string name="toast_asset_copy_failed">فشل نسخ الملف، يرجى استخدام مدير الملفات</string>
|
||||||
|
|||||||
@@ -117,7 +117,11 @@
|
|||||||
<string name="server_lab_xhttp_extra">XHTTP Extra raw JSON, format: { XHTTPObject }</string>
|
<string name="server_lab_xhttp_extra">XHTTP Extra raw JSON, format: { XHTTPObject }</string>
|
||||||
<string name="server_lab_final_mask">finalMask raw JSON, format: { FinalMaskObject }</string>
|
<string name="server_lab_final_mask">finalMask raw JSON, format: { FinalMaskObject }</string>
|
||||||
<string name="server_lab_ech_config_list">EchConfigList</string>
|
<string name="server_lab_ech_config_list">EchConfigList</string>
|
||||||
|
<string name="server_lab_verify_peer_cert_by_name">Verify Peer Cert By Name</string>
|
||||||
<string name="server_lab_pinned_ca256">Certificate fingerprint (SHA-256)</string>
|
<string name="server_lab_pinned_ca256">Certificate fingerprint (SHA-256)</string>
|
||||||
|
<string name="pinned_ca256_action_fetch">Fetch and fill certificate fingerprint</string>
|
||||||
|
<string name="toast_fetch_cert_sha256_success">Certificate fingerprint fetched successfully</string>
|
||||||
|
<string name="toast_fetch_cert_sha256_failed">Failed to fetch certificate fingerprint</string>
|
||||||
|
|
||||||
<!-- UserAssetActivity -->
|
<!-- UserAssetActivity -->
|
||||||
<string name="toast_asset_copy_failed">ফাইল কপি ব্যর্থ, অনুগ্রহ করে ফাইল ম্যানেজার ব্যবহার করুন</string>
|
<string name="toast_asset_copy_failed">ফাইল কপি ব্যর্থ, অনুগ্রহ করে ফাইল ম্যানেজার ব্যবহার করুন</string>
|
||||||
|
|||||||
@@ -117,7 +117,11 @@
|
|||||||
<string name="server_lab_xhttp_extra">XHTTP Extra خام JSON، قالوو: { XHTTPObject }</string>
|
<string name="server_lab_xhttp_extra">XHTTP Extra خام JSON، قالوو: { XHTTPObject }</string>
|
||||||
<string name="server_lab_final_mask">finalMask خام JSON، قالوو: { FinalMaskObject }</string>
|
<string name="server_lab_final_mask">finalMask خام JSON، قالوو: { FinalMaskObject }</string>
|
||||||
<string name="server_lab_ech_config_list">نومگه کانفیگ Ech</string>
|
<string name="server_lab_ech_config_list">نومگه کانفیگ Ech</string>
|
||||||
<string name="server_lab_pinned_ca256">Certificate fingerprint (SHA-256)</string>
|
<string name="server_lab_verify_peer_cert_by_name">تئیڌ گوواهی هومتا و ری نوم</string>
|
||||||
|
<string name="server_lab_pinned_ca256">جا کلک گوواهی (SHA-256)</string>
|
||||||
|
<string name="pinned_ca256_action_fetch">Fetch and fill certificate fingerprint</string>
|
||||||
|
<string name="toast_fetch_cert_sha256_success">Certificate fingerprint fetched successfully</string>
|
||||||
|
<string name="toast_fetch_cert_sha256_failed">Failed to fetch certificate fingerprint</string>
|
||||||
|
|
||||||
<!-- UserAssetActivity -->
|
<!-- UserAssetActivity -->
|
||||||
<string name="toast_asset_copy_failed">لف گیری فایل ٱنجوم نوابی، ز ی برنومه دؽوۉداری فایل هیاری بگرین</string>
|
<string name="toast_asset_copy_failed">لف گیری فایل ٱنجوم نوابی، ز ی برنومه دؽوۉداری فایل هیاری بگرین</string>
|
||||||
@@ -357,7 +361,7 @@
|
|||||||
<string name="connection_not_connected">منپیز نؽڌ</string>
|
<string name="connection_not_connected">منپیز نؽڌ</string>
|
||||||
<string name="connection_runing_task_left">تعداد وزیفه یل آزمایشی ک ره اۊفتانه: %s</string>
|
<string name="connection_runing_task_left">تعداد وزیفه یل آزمایشی ک ره اۊفتانه: %s</string>
|
||||||
|
|
||||||
<string name="import_subscription_success">اشتراک وا موفقیت زفت زابی</string>
|
<string name="import_subscription_success">اشتراک وا مووفقیت زفت وابی</string>
|
||||||
<string name="import_subscription_failure">اشتراک زفت نوابی</string>
|
<string name="import_subscription_failure">اشتراک زفت نوابی</string>
|
||||||
<string name="title_fragment_settings">سامووا فرگمنت</string>
|
<string name="title_fragment_settings">سامووا فرگمنت</string>
|
||||||
<string name="title_pref_fragment_packets">کتنا فرگمنت</string>
|
<string name="title_pref_fragment_packets">کتنا فرگمنت</string>
|
||||||
@@ -445,7 +449,7 @@
|
|||||||
</string-array>
|
</string-array>
|
||||||
<string-array name="policy_group_type">
|
<string-array name="policy_group_type">
|
||||||
<item>کم ترین پینگ</item>
|
<item>کم ترین پینگ</item>
|
||||||
<item>کم ترین بار(لود)</item>
|
<item>کم ترین بار (لود)</item>
|
||||||
<item>تساڌۊفی</item>
|
<item>تساڌۊفی</item>
|
||||||
<item>گردشی</item>
|
<item>گردشی</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|||||||
@@ -117,7 +117,11 @@
|
|||||||
<string name="server_lab_xhttp_extra">خام JSON XHTTP Extra، قالب: { XHTTPObject }</string>
|
<string name="server_lab_xhttp_extra">خام JSON XHTTP Extra، قالب: { XHTTPObject }</string>
|
||||||
<string name="server_lab_final_mask">finalMask raw JSON, format: { FinalMaskObject }</string>
|
<string name="server_lab_final_mask">finalMask raw JSON, format: { FinalMaskObject }</string>
|
||||||
<string name="server_lab_ech_config_list">لیست کانفیگ Ech</string>
|
<string name="server_lab_ech_config_list">لیست کانفیگ Ech</string>
|
||||||
|
<string name="server_lab_verify_peer_cert_by_name">Verify Peer Cert By Name</string>
|
||||||
<string name="server_lab_pinned_ca256">Certificate fingerprint (SHA-256)</string>
|
<string name="server_lab_pinned_ca256">Certificate fingerprint (SHA-256)</string>
|
||||||
|
<string name="pinned_ca256_action_fetch">Fetch and fill certificate fingerprint</string>
|
||||||
|
<string name="toast_fetch_cert_sha256_success">Certificate fingerprint fetched successfully</string>
|
||||||
|
<string name="toast_fetch_cert_sha256_failed">Failed to fetch certificate fingerprint</string>
|
||||||
|
|
||||||
<!-- UserAssetActivity -->
|
<!-- UserAssetActivity -->
|
||||||
<string name="toast_asset_copy_failed">کپی فایل انجام نشد، لطفا از برنامه مدیریت فایل استفاده کنید</string>
|
<string name="toast_asset_copy_failed">کپی فایل انجام نشد، لطفا از برنامه مدیریت فایل استفاده کنید</string>
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
<string name="menu_item_import_config_qrcode">Импорт из QR-кода</string>
|
<string name="menu_item_import_config_qrcode">Импорт из QR-кода</string>
|
||||||
<string name="menu_item_import_config_clipboard">Импорт из буфера обмена</string>
|
<string name="menu_item_import_config_clipboard">Импорт из буфера обмена</string>
|
||||||
<string name="menu_item_import_config_local">Импорт из файла</string>
|
<string name="menu_item_import_config_local">Импорт из файла</string>
|
||||||
<string name="menu_item_import_config_policy_group">Добавить группу политик</string>
|
<string name="menu_item_import_config_policy_group">Добавить политику группы</string>
|
||||||
<string name="menu_item_import_config_proxy_chain">Добавить цепочку прокси</string>
|
<string name="menu_item_import_config_proxy_chain">Добавить цепочку прокси</string>
|
||||||
<string name="menu_item_import_config_manually_vmess">Добавить VMess</string>
|
<string name="menu_item_import_config_manually_vmess">Добавить VMess</string>
|
||||||
<string name="menu_item_import_config_manually_vless">Добавить VLESS</string>
|
<string name="menu_item_import_config_manually_vless">Добавить VLESS</string>
|
||||||
@@ -117,9 +117,14 @@
|
|||||||
<string name="server_lab_xhttp_extra">Необработанный JSON XHTTP Extra, формат: { XHTTPObject }</string>
|
<string name="server_lab_xhttp_extra">Необработанный JSON XHTTP Extra, формат: { XHTTPObject }</string>
|
||||||
<string name="server_lab_final_mask">Необработанный JSON FinalMask, формат: { FinalMaskObject }</string>
|
<string name="server_lab_final_mask">Необработанный JSON FinalMask, формат: { FinalMaskObject }</string>
|
||||||
<string name="server_lab_ech_config_list">ECHConfigList</string>
|
<string name="server_lab_ech_config_list">ECHConfigList</string>
|
||||||
|
<string name="server_lab_verify_peer_cert_by_name">Проверять сертификат узла по имени</string>
|
||||||
<string name="server_lab_pinned_ca256">Отпечаток сертификата (SHA-256)</string>
|
<string name="server_lab_pinned_ca256">Отпечаток сертификата (SHA-256)</string>
|
||||||
<string name="server_lab_browser_dialer">Использовать переадресацию браузера</string>
|
<string name="server_lab_browser_dialer">Использовать переадресацию браузера</string>
|
||||||
<string name="server_lab_browser_dialer_tip">Поддерживаются только исходящие соединения XHTTP (packet-up) и WS; настройки, связанные с TLS, могут быть проигнорированы или конфликтовать</string>
|
<string name="server_lab_browser_dialer_tip">Поддерживаются только исходящие соединения XHTTP (packet-up) и WS; настройки, связанные с TLS, могут быть проигнорированы или конфликтовать</string>
|
||||||
|
<string name="toast_allow_insecure_deprecated">ВНИМАНИЕ: пропуск проверки сертификата («Разрешать небезопасные соединения») будет отключён в августе 2026 года. Пожалуйста, используйте отпечатки сертификатов как можно скорее. Функция будет недоступна после истечения срока действия.</string>
|
||||||
|
<string name="pinned_ca256_action_fetch">Fetch and fill certificate fingerprint</string>
|
||||||
|
<string name="toast_fetch_cert_sha256_success">Certificate fingerprint fetched successfully</string>
|
||||||
|
<string name="toast_fetch_cert_sha256_failed">Failed to fetch certificate fingerprint</string>
|
||||||
|
|
||||||
<!-- UserAssetActivity -->
|
<!-- UserAssetActivity -->
|
||||||
<string name="toast_asset_copy_failed">Невозможно скопировать файл, используйте файловый менеджер</string>
|
<string name="toast_asset_copy_failed">Невозможно скопировать файл, используйте файловый менеджер</string>
|
||||||
@@ -288,7 +293,7 @@
|
|||||||
<string name="title_logcat">Журнал</string>
|
<string name="title_logcat">Журнал</string>
|
||||||
<string name="logcat_copy">Копировать</string>
|
<string name="logcat_copy">Копировать</string>
|
||||||
<string name="logcat_clear">Очистить</string>
|
<string name="logcat_clear">Очистить</string>
|
||||||
<string name="logcat_share">Поделиться логом</string>
|
<string name="logcat_share">Поделиться журналом</string>
|
||||||
<string name="title_service_restart">Перезапуск службы</string>
|
<string name="title_service_restart">Перезапуск службы</string>
|
||||||
<string name="title_del_all_config">Удалить профили</string>
|
<string name="title_del_all_config">Удалить профили</string>
|
||||||
<string name="title_del_duplicate_config">Удалить дубликаты профилей</string>
|
<string name="title_del_duplicate_config">Удалить дубликаты профилей</string>
|
||||||
@@ -377,7 +382,7 @@
|
|||||||
<string name="update_check_pre_release">Искать предварительный выпуск</string>
|
<string name="update_check_pre_release">Искать предварительный выпуск</string>
|
||||||
<string name="update_checking_for_update">Проверка обновления…</string>
|
<string name="update_checking_for_update">Проверка обновления…</string>
|
||||||
|
|
||||||
<string name="title_policy_group_type">Тип группы политик</string>
|
<string name="title_policy_group_type">Тип политики группы</string>
|
||||||
<string name="title_policy_group_subscription_id">Из группы подписки</string>
|
<string name="title_policy_group_subscription_id">Из группы подписки</string>
|
||||||
<string name="title_policy_group_subscription_filter">Название фильтра</string>
|
<string name="title_policy_group_subscription_filter">Название фильтра</string>
|
||||||
|
|
||||||
@@ -424,6 +429,12 @@
|
|||||||
<item>Прокси</item>
|
<item>Прокси</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
|
<string-array name="browser_dialer_mode">
|
||||||
|
<item>Отключено</item>
|
||||||
|
<item>OkHttp</item>
|
||||||
|
<item>WebView</item>
|
||||||
|
</string-array>
|
||||||
|
|
||||||
<string-array name="ui_mode_night">
|
<string-array name="ui_mode_night">
|
||||||
<item>Как в системе</item>
|
<item>Как в системе</item>
|
||||||
<item>Светлая</item>
|
<item>Светлая</item>
|
||||||
|
|||||||
@@ -117,7 +117,11 @@
|
|||||||
<string name="server_lab_xhttp_extra">XHTTP Extra raw JSON, format: { XHTTPObject }</string>
|
<string name="server_lab_xhttp_extra">XHTTP Extra raw JSON, format: { XHTTPObject }</string>
|
||||||
<string name="server_lab_final_mask">finalMask raw JSON, format: { FinalMaskObject }</string>
|
<string name="server_lab_final_mask">finalMask raw JSON, format: { FinalMaskObject }</string>
|
||||||
<string name="server_lab_ech_config_list">EchConfigList</string>
|
<string name="server_lab_ech_config_list">EchConfigList</string>
|
||||||
|
<string name="server_lab_verify_peer_cert_by_name">Verify Peer Cert By Name</string>
|
||||||
<string name="server_lab_pinned_ca256">Certificate fingerprint (SHA-256)</string>
|
<string name="server_lab_pinned_ca256">Certificate fingerprint (SHA-256)</string>
|
||||||
|
<string name="pinned_ca256_action_fetch">Fetch and fill certificate fingerprint</string>
|
||||||
|
<string name="toast_fetch_cert_sha256_success">Certificate fingerprint fetched successfully</string>
|
||||||
|
<string name="toast_fetch_cert_sha256_failed">Failed to fetch certificate fingerprint</string>
|
||||||
|
|
||||||
<!-- UserAssetActivity -->
|
<!-- UserAssetActivity -->
|
||||||
<string name="toast_asset_copy_failed">Không thể sao chép tệp tin, hãy dùng trình quản lý tệp!</string>
|
<string name="toast_asset_copy_failed">Không thể sao chép tệp tin, hãy dùng trình quản lý tệp!</string>
|
||||||
|
|||||||
@@ -117,10 +117,14 @@
|
|||||||
<string name="server_lab_xhttp_extra">XHTTP Extra 原始 JSON,格式: { XHTTPObject }</string>
|
<string name="server_lab_xhttp_extra">XHTTP Extra 原始 JSON,格式: { XHTTPObject }</string>
|
||||||
<string name="server_lab_final_mask">FinalMask 原始 JSON 格式: { FinalMaskObject }</string>
|
<string name="server_lab_final_mask">FinalMask 原始 JSON 格式: { FinalMaskObject }</string>
|
||||||
<string name="server_lab_ech_config_list">EchConfigList</string>
|
<string name="server_lab_ech_config_list">EchConfigList</string>
|
||||||
|
<string name="server_lab_verify_peer_cert_by_name">Verify Peer Cert By Name</string>
|
||||||
<string name="server_lab_pinned_ca256">证书指纹 (SHA-256)</string>
|
<string name="server_lab_pinned_ca256">证书指纹 (SHA-256)</string>
|
||||||
<string name="server_lab_browser_dialer">启用浏览器转发</string>
|
<string name="server_lab_browser_dialer">启用浏览器转发</string>
|
||||||
<string name="server_lab_browser_dialer_tip">仅支持 xhttp (packet-up) 和 ws。与优选域名冲突,utls, alpn, ech 等 TLS 设置将被忽略</string>
|
<string name="server_lab_browser_dialer_tip">仅支持 xhttp (packet-up) 和 ws。与优选域名冲突,utls, alpn, ech 等 TLS 设置将被忽略</string>
|
||||||
<string name="toast_allow_insecure_deprecated">警告:将在 2026.8.1 禁用跳过证书验证 "allowInsecure" ,请尽快改用证书指纹。到期后无法使用。 </string>
|
<string name="toast_allow_insecure_deprecated">警告:将在 2026.8.1 移除跳过证书验证 "allowInsecure" ,请尽快改用证书指纹或修改服务端。否则到期后会无法使用。 </string>
|
||||||
|
<string name="pinned_ca256_action_fetch">获取并填充指纹证书</string>
|
||||||
|
<string name="toast_fetch_cert_sha256_success">获取指纹证书完成</string>
|
||||||
|
<string name="toast_fetch_cert_sha256_failed">获取证书指纹失败</string>
|
||||||
|
|
||||||
<!-- UserAssetActivity -->
|
<!-- UserAssetActivity -->
|
||||||
<string name="toast_asset_copy_failed">失败, 请使用文件管理器</string>
|
<string name="toast_asset_copy_failed">失败, 请使用文件管理器</string>
|
||||||
|
|||||||
@@ -117,8 +117,12 @@
|
|||||||
<string name="server_lab_xhttp_extra">XHTTP Extra 原始 JSON,格式: { XHTTPObject }</string>
|
<string name="server_lab_xhttp_extra">XHTTP Extra 原始 JSON,格式: { XHTTPObject }</string>
|
||||||
<string name="server_lab_final_mask">FinalMask 原始 JSON 格式: { FinalMaskObject }</string>
|
<string name="server_lab_final_mask">FinalMask 原始 JSON 格式: { FinalMaskObject }</string>
|
||||||
<string name="server_lab_ech_config_list">EchConfigList</string>
|
<string name="server_lab_ech_config_list">EchConfigList</string>
|
||||||
|
<string name="server_lab_verify_peer_cert_by_name">Verify Peer Cert By Name</string>
|
||||||
<string name="server_lab_pinned_ca256">證書指紋 (SHA-256)</string>
|
<string name="server_lab_pinned_ca256">證書指紋 (SHA-256)</string>
|
||||||
<string name="toast_allow_insecure_deprecated">警告:將在 2026.8.1 停用跳過憑證驗證 "allowInsecure" ,請盡快改用憑證指紋。到期後無法使用。 </string>
|
<string name="toast_allow_insecure_deprecated">警告:將在 2026.8.1 停用跳過憑證驗證 "allowInsecure" ,請盡快改用憑證指紋。到期後無法使用。 </string>
|
||||||
|
<string name="pinned_ca256_action_fetch">獲取並填充指紋證書</string>
|
||||||
|
<string name="toast_fetch_cert_sha256_success">獲取指紋證書完成</string>
|
||||||
|
<string name="toast_fetch_cert_sha256_failed">獲取證書指紋失敗</string>
|
||||||
|
|
||||||
<!-- UserAssetActivity -->
|
<!-- UserAssetActivity -->
|
||||||
<string name="toast_asset_copy_failed">失敗,請使用檔案總管</string>
|
<string name="toast_asset_copy_failed">失敗,請使用檔案總管</string>
|
||||||
|
|||||||
@@ -64,6 +64,7 @@
|
|||||||
|
|
||||||
<string-array name="fragment_packets" translatable="false">
|
<string-array name="fragment_packets" translatable="false">
|
||||||
<item>tlshello</item>
|
<item>tlshello</item>
|
||||||
|
<item>1-1</item>
|
||||||
<item>1-2</item>
|
<item>1-2</item>
|
||||||
<item>1-3</item>
|
<item>1-3</item>
|
||||||
<item>1-5</item>
|
<item>1-5</item>
|
||||||
|
|||||||
@@ -118,10 +118,14 @@
|
|||||||
<string name="server_lab_xhttp_extra">XHTTP Extra raw JSON, format: { XHTTPObject }</string>
|
<string name="server_lab_xhttp_extra">XHTTP Extra raw JSON, format: { XHTTPObject }</string>
|
||||||
<string name="server_lab_final_mask">finalMask raw JSON, format: { FinalMaskObject }</string>
|
<string name="server_lab_final_mask">finalMask raw JSON, format: { FinalMaskObject }</string>
|
||||||
<string name="server_lab_ech_config_list">EchConfigList</string>
|
<string name="server_lab_ech_config_list">EchConfigList</string>
|
||||||
|
<string name="server_lab_verify_peer_cert_by_name">Verify Peer Cert By Name</string>
|
||||||
<string name="server_lab_pinned_ca256">Certificate fingerprint (SHA-256)</string>
|
<string name="server_lab_pinned_ca256">Certificate fingerprint (SHA-256)</string>
|
||||||
<string name="server_lab_browser_dialer">Enable Browser Dialer</string>
|
<string name="server_lab_browser_dialer">Enable Browser Dialer</string>
|
||||||
<string name="server_lab_browser_dialer_tip">Only supports xhttp (packet-up) and ws outbound; TLS-related settings may be ignored or conflict</string>
|
<string name="server_lab_browser_dialer_tip">Only supports xhttp (packet-up) and ws outbound; TLS-related settings may be ignored or conflict</string>
|
||||||
<string name="toast_allow_insecure_deprecated">WARNING: Skipping certificate verification "allowInsecure" will be disabled in August 2026. Please use certificate fingerprints as soon as possible. This feature will not be available after its expiration.</string>
|
<string name="toast_allow_insecure_deprecated">WARNING: Skipping certificate verification "allowInsecure" will be disabled in August 2026. Please use certificate fingerprints as soon as possible. This feature will not be available after its expiration.</string>
|
||||||
|
<string name="pinned_ca256_action_fetch">Fetch and fill certificate fingerprint</string>
|
||||||
|
<string name="toast_fetch_cert_sha256_success">Certificate fingerprint fetched successfully</string>
|
||||||
|
<string name="toast_fetch_cert_sha256_failed">Failed to fetch certificate fingerprint</string>
|
||||||
|
|
||||||
<!-- UserAssetActivity -->
|
<!-- UserAssetActivity -->
|
||||||
<string name="toast_asset_copy_failed">File copy failed, please use File Manager</string>
|
<string name="toast_asset_copy_failed">File copy failed, please use File Manager</string>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
agp = "9.2.1"
|
agp = "9.2.1"
|
||||||
desugarJdkLibs = "2.1.5"
|
desugarJdkLibs = "2.1.5"
|
||||||
gradleLicensePlugin = "0.9.8"
|
gradleLicensePlugin = "0.9.8"
|
||||||
kotlin = "2.3.21"
|
kotlin = "2.4.0"
|
||||||
coreKtx = "1.17.0"
|
coreKtx = "1.17.0"
|
||||||
junit = "4.13.2"
|
junit = "4.13.2"
|
||||||
junitVersion = "1.3.0"
|
junitVersion = "1.3.0"
|
||||||
|
|||||||
Reference in new issue
Block a user