mirror of
https://github.com/2dust/v2rayNG.git
synced 2025-06-28 12:19:52 +00:00
Fix the parsing problem of non-English domain
https://github.com/2dust/v2rayNG/issues/4626
This commit is contained in:
parent
aa47fba20d
commit
f305e26a39
10 changed files with 45 additions and 18 deletions
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
*
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue