Follow encodeURIComponent rules strictly (#5250)

This commit is contained in:
DHR60 authored and GitHub committed 2026-02-06 19:38:45 +08:00
1 parent 74f701096e
commit f48253371d
9 files changed
+48 -13

No files matched your search

@@ -22,17 +22,17 @@ open class FmtBase {
val query = if (dicQuery != null)
"?" + dicQuery.toList().joinToString(
separator = "&",
transform = { it.first + "=" + Utils.urlEncode(it.second) })
transform = { it.first + "=" + Utils.encodeURIComponent(it.second) })
else ""
val url = String.format(
"%s@%s:%s",
Utils.urlEncode(userInfo ?: ""),
Utils.encodeURIComponent(userInfo ?: ""),
Utils.getIpv6Address(HttpUtil.toIdnDomain(config.server.orEmpty())),
config.serverPort
)
return "${url}${query}#${Utils.urlEncode(config.remarks)}"
return "${url}${query}#${Utils.encodeURIComponent(config.remarks)}"
}
/**
@@ -43,7 +43,7 @@ open class FmtBase {
*/
fun getQueryParam(uri: URI): Map<String, String> {
return uri.rawQuery.split("&")
.associate { it.split("=").let { (k, v) -> k to Utils.urlDecode(v) } }
.associate { it.split("=").let { (k, v) -> k to Utils.decodeURIComponent(v) } }
}
/**
@@ -26,7 +26,7 @@ object Hysteria2Fmt : FmtBase() {
val config = ProfileItem.create(EConfigType.HYSTERIA2)
val uri = URI(Utils.fixIllegalUrl(str))
config.remarks = Utils.urlDecode(uri.fragment.orEmpty()).let { it.ifEmpty { "none" } }
config.remarks = Utils.decodeURIComponent(uri.fragment.orEmpty()).let { it.ifEmpty { "none" } }
config.server = uri.idnHost
config.serverPort = uri.port.toString()
config.password = uri.userInfo
@@ -36,7 +36,7 @@ object ShadowsocksFmt : FmtBase() {
if (uri.port <= 0) return null
if (uri.userInfo.isNullOrEmpty()) return null
config.remarks = Utils.urlDecode(uri.fragment.orEmpty()).let { it.ifEmpty { "none" } }
config.remarks = Utils.decodeURIComponent(uri.fragment.orEmpty()).let { it.ifEmpty { "none" } }
config.server = uri.idnHost
config.serverPort = uri.port.toString()
@@ -83,7 +83,7 @@ object ShadowsocksFmt : FmtBase() {
if (indexSplit > 0) {
try {
config.remarks =
Utils.urlDecode(result.substring(indexSplit + 1, result.length))
Utils.decodeURIComponent(result.substring(indexSplit + 1, result.length))
} catch (e: Exception) {
Log.e(AppConfig.TAG, "Failed to decode remarks in SS legacy URL", e)
}
@@ -23,7 +23,7 @@ object SocksFmt : FmtBase() {
if (uri.idnHost.isEmpty()) return null
if (uri.port <= 0) return null
config.remarks = Utils.urlDecode(uri.fragment.orEmpty()).let { it.ifEmpty { "none" } }
config.remarks = Utils.decodeURIComponent(uri.fragment.orEmpty()).let { it.ifEmpty { "none" } }
config.server = uri.idnHost
config.serverPort = uri.port.toString()
@@ -23,7 +23,7 @@ object TrojanFmt : FmtBase() {
val config = ProfileItem.create(EConfigType.TROJAN)
val uri = URI(Utils.fixIllegalUrl(str))
config.remarks = Utils.urlDecode(uri.fragment.orEmpty()).let { it.ifEmpty { "none" } }
config.remarks = Utils.decodeURIComponent(uri.fragment.orEmpty()).let { it.ifEmpty { "none" } }
config.server = uri.idnHost
config.serverPort = uri.port.toString()
config.password = uri.userInfo
@@ -26,7 +26,7 @@ object VlessFmt : FmtBase() {
if (uri.rawQuery.isNullOrEmpty()) return null
val queryParam = getQueryParam(uri)
config.remarks = Utils.urlDecode(uri.fragment.orEmpty()).let { it.ifEmpty { "none" } }
config.remarks = Utils.decodeURIComponent(uri.fragment.orEmpty()).let { it.ifEmpty { "none" } }
config.server = uri.idnHost
config.serverPort = uri.port.toString()
config.password = uri.userInfo
@@ -163,7 +163,7 @@ object VmessFmt : FmtBase() {
if (uri.rawQuery.isNullOrEmpty()) return null
val queryParam = getQueryParam(uri)
config.remarks = Utils.urlDecode(uri.fragment.orEmpty()).let { it.ifEmpty { "none" } }
config.remarks = Utils.decodeURIComponent(uri.fragment.orEmpty()).let { it.ifEmpty { "none" } }
config.server = uri.idnHost
config.serverPort = uri.port.toString()
config.password = uri.userInfo
@@ -26,7 +26,7 @@ object WireguardFmt : FmtBase() {
if (uri.rawQuery.isNullOrEmpty()) return null
val queryParam = getQueryParam(uri)
config.remarks = Utils.urlDecode(uri.fragment.orEmpty()).let { it.ifEmpty { "none" } }
config.remarks = Utils.decodeURIComponent(uri.fragment.orEmpty()).let { it.ifEmpty { "none" } }
config.server = uri.idnHost
config.serverPort = uri.port.toString()
@@ -328,13 +328,48 @@ object Utils {
*/
fun urlEncode(url: String): String {
return try {
URLEncoder.encode(url, Charsets.UTF_8.toString()).replace("+", "%20")
URLEncoder.encode(url, Charsets.UTF_8.toString())
} catch (e: Exception) {
Log.e(AppConfig.TAG, "Failed to encode URL", e)
url
}
}
/**
* Decode a "encodeURIComponent" string.
*
* @param url The "encodeURIComponent" string.
* @return The decoded string, or the original string if decoding fails.
*/
fun decodeURIComponent(url: String): String {
return try {
// Decode strictly according to RFC 3986 / encodeURIComponent semantics.
// '+' is a literal plus and MUST NOT be interpreted as space.
// Inputs using '+' for spaces are non-conforming and rejected deliberately
// to avoid cross-language interoperability issues.
URLDecoder.decode(url.replace("+", "%2B"), Charsets.UTF_8.toString())
} catch (e: Exception) {
Log.e(AppConfig.TAG, "Failed to decode encodeURIComponent", e)
url
}
}
/**
* Encode a string to "encodeURIComponent" format.
*
* @param url The string to encode.
* @return The "encodeURIComponent" encoded string, or the original string if encoding fails.
*/
fun encodeURIComponent(url: String): String {
return try {
// Replace '+' with '%20' to conform to encodeURIComponent semantics.
URLEncoder.encode(url, Charsets.UTF_8.toString()).replace("+", "%20")
} catch (e: Exception) {
Log.e(AppConfig.TAG, "Failed to encode encodeURIComponent", e)
url
}
}
/**
* Read text from an asset file.
*