owenclave / app/src/main/java/io/nekohasekai/sagernet/group/V2RayJSONParser.kt

github - dev - back to tree - upstream
/****************************************************************************** * * * Copyright (C) 2023 dyhkwong * * Copyright (C) 2021 by nekohasekai * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 3 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * * * ******************************************************************************/ package io.nekohasekai.sagernet.group import com.github.shadowsocks.plugin.PluginOptions import com.google.gson.Gson import com.google.gson.GsonBuilder import com.google.gson.JsonArray import com.google.gson.JsonObject import io.nekohasekai.sagernet.database.DataStore import io.nekohasekai.sagernet.fmt.AbstractBean import io.nekohasekai.sagernet.fmt.anytls.AnyTLSBean import io.nekohasekai.sagernet.fmt.http.HttpBean import io.nekohasekai.sagernet.fmt.http3.Http3Bean import io.nekohasekai.sagernet.fmt.hysteria2.Hysteria2Bean import io.nekohasekai.sagernet.fmt.juicity.JuicityBean import io.nekohasekai.sagernet.fmt.mieru.MieruBean import io.nekohasekai.sagernet.fmt.shadowsocks.ShadowsocksBean import io.nekohasekai.sagernet.fmt.shadowsocks.supportedShadowsocks2022Method import io.nekohasekai.sagernet.fmt.shadowsocks.supportedShadowsocksMethod import io.nekohasekai.sagernet.fmt.snell.SnellBean import io.nekohasekai.sagernet.fmt.socks.SOCKSBean import io.nekohasekai.sagernet.fmt.ssh.SSHBean import io.nekohasekai.sagernet.fmt.trojan.TrojanBean import io.nekohasekai.sagernet.fmt.trusttunnel.TrustTunnelBean import io.nekohasekai.sagernet.fmt.tuic5.Tuic5Bean import io.nekohasekai.sagernet.fmt.tuic5.supportedTuic5CongestionControl import io.nekohasekai.sagernet.fmt.tuic5.supportedTuic5RelayMode import io.nekohasekai.sagernet.fmt.v2ray.VLESSBean import io.nekohasekai.sagernet.fmt.v2ray.VMessBean import io.nekohasekai.sagernet.fmt.v2ray.legacyVlessFlow import io.nekohasekai.sagernet.fmt.v2ray.nonRawTransportName import io.nekohasekai.sagernet.fmt.v2ray.supportedKcpQuicHeaderType import io.nekohasekai.sagernet.fmt.v2ray.supportedQuicSecurity import io.nekohasekai.sagernet.fmt.v2ray.supportedVlessFlow import io.nekohasekai.sagernet.fmt.v2ray.supportedVmessMethod import io.nekohasekai.sagernet.fmt.v2ray.supportedXhttpMode import io.nekohasekai.sagernet.fmt.wireguard.WireGuardBean import io.nekohasekai.sagernet.ktx.* import libexclavecore.Libexclavecore import java.util.Base64 fun parseV2RayOutbound(outbound: JsonObject): List { // v2ray JSONv4 config, Xray config and JSONv4 config of Owenclave's v2ray fork only when (val proto = outbound.getString("protocol")?.lowercase()) { "vmess", "vless", "trojan", "shadowsocks", "socks", "http", "shadowsocks2022", "shadowsocks-2022" -> { val v2rayBean = when (proto) { "vmess" -> VMessBean() "vless" -> VLESSBean() "trojan" -> TrojanBean() "shadowsocks", "shadowsocks2022", "shadowsocks-2022" -> ShadowsocksBean() "socks" -> SOCKSBean() else -> HttpBean() } outbound.getObject("streamSettings")?.also { streamSettings -> streamSettings.getString("security")?.lowercase()?.also { security -> when (security) { "tls", "utls", "xtls" -> { v2rayBean.security = "tls" var tlsConfig = streamSettings.getObject("tlsSettings") if (security == "utls") { streamSettings.getObject("utlsSettings")?.also { tlsConfig = it.getObject("tlsConfig") } } if (security == "xtls") { // old Xray streamSettings.getObject("xtlsSettings")?.also { tlsConfig = it } } tlsConfig?.also { tlsSettings -> tlsSettings.getString("serverName")?.also { v2rayBean.sni = it } tlsSettings.getStringArray("alpn")?.also { v2rayBean.alpn = it.joinToString("\n") } ?: tlsSettings.getString("alpn")?.also { v2rayBean.alpn = it.split(",").joinToString("\n") } tlsSettings.getBoolean("allowInsecure")?.also { v2rayBean.allowInsecure = it } tlsSettings.getArray("certificates")?.asReversed()?.forEach { certificate -> when (certificate.getString("usage")?.lowercase()) { null, "", "encipherment" -> { if (!certificate.contains("certificateFile") && !certificate.contains("keyFile")) { val cert = certificate.getStringArray("certificate")?.joinToString("\n")?.takeIf { it.contains("-----BEGIN ") && it.contains("-----END ") && it.contains(" CERTIFICATE-----") } val key = certificate.getStringArray("key")?.joinToString("\n")?.takeIf { it.contains("-----BEGIN ") && it.contains("-----END ") && it.contains(" PRIVATE KEY-----") } if (cert != null && key != null) { v2rayBean.mtlsCertificate = cert v2rayBean.mtlsCertificatePrivateKey = key } } } "verify" -> { if (!certificate.contains("certificateFile")) { val cert = certificate.getStringArray("certificate")?.joinToString("\n")?.takeIf { it.contains("-----BEGIN ") && it.contains("-----END ") && it.contains(" CERTIFICATE-----") } if (cert != null) { v2rayBean.certificates = cert } } } } } tlsSettings.getStringArray("pinnedPeerCertificateChainSha256")?.also { v2rayBean.pinnedPeerCertificateChainSha256 = it.joinToString("\n") tlsSettings.getBoolean("allowInsecureIfPinnedPeerCertificate")?.also { allowInsecure -> v2rayBean.allowInsecure = allowInsecure } } tlsSettings.getStringArray("pinnedPeerCertificatePublicKeySha256")?.also { v2rayBean.pinnedPeerCertificatePublicKeySha256 = it.joinToString("\n") tlsSettings.getBoolean("allowInsecureIfPinnedPeerCertificate")?.also { allowInsecure -> v2rayBean.allowInsecure = allowInsecure } } tlsSettings.getStringArray("pinnedPeerCertificateSha256")?.also { v2rayBean.pinnedPeerCertificateSha256 = it.joinToString("\n") tlsSettings.getBoolean("allowInsecureIfPinnedPeerCertificate")?.also { allowInsecure -> v2rayBean.allowInsecure = allowInsecure } } tlsSettings.getString("pinnedPeerCertSha256")?.takeIf { it.isNotEmpty() }?.also { pcs -> // https://github.com/XTLS/Xray-core/commit/0ca13452b8e99824e08c2c860dd0f84a4ae2859d v2rayBean.pinnedPeerCertificateSha256 = pcs.split(",") .mapNotNull { it.trim().ifEmpty { null }?.replace(":", "") } .joinToString("\n") if (!v2rayBean.pinnedPeerCertificateSha256.isNullOrEmpty()) { v2rayBean.allowInsecure = true } } tlsSettings.getString("verifyPeerCertByName")?.split(",") ?.filter { it.isNotEmpty() }?.takeIf { it.isNotEmpty() }?.also { // Xray verifyPeerCertByName v2rayBean.serverNameToVerify = it.joinToString("\n") } tlsSettings.getStringArray("serverNameToVerify")?.also { v2rayBean.serverNameToVerify = it.joinToString("\n") } if (v2rayBean is VLESSBean || v2rayBean is TrojanBean || v2rayBean is VMessBean) { tlsSettings.getString("echDohServer")?.also { v2rayBean.echEnabled = true } tlsSettings.getString("echConfig")?.also { v2rayBean.echEnabled = true v2rayBean.echConfig = it } tlsSettings.getString("echConfigList")?.also { v2rayBean.echEnabled = true try { Base64.getDecoder().decode(it) v2rayBean.echConfig = it } catch (_: Exception) {} } tlsSettings.getObject("ech")?.also { v2rayBean.echEnabled = it.getBoolean("enabled") v2rayBean.echConfig = it.getString("config") } } } } "reality" -> { v2rayBean.security = "reality" streamSettings.getObject("realitySettings")?.also { realitySettings -> realitySettings.getString("serverName")?.also { v2rayBean.sni = it } realitySettings.getString("publicKey")?.also { v2rayBean.realityPublicKey = it } realitySettings.getString("password")?.takeIf { it.isNotEmpty() }?.also { v2rayBean.realityPublicKey = it } if (v2rayBean.realityPublicKey.isNullOrEmpty()) { return listOf() } realitySettings.getString("shortId")?.also { v2rayBean.realityShortId = it } realitySettings.getString("mldsa65Verify")?.also { v2rayBean.realityMldsa65Verify = it } } } } } (streamSettings.getString("method") ?: streamSettings.getString("network"))?.lowercase()?.also { network -> when (network) { "tcp", "raw" -> { v2rayBean.type = "tcp" streamSettings.getObject("finalmask")?.also { finalmask -> // ban Xray TCP finalmask finalmask.getArray("tcp")?.takeIf { it.isNotEmpty() }?.also { return listOf() } } (streamSettings.getObject("tcpSettings") ?: streamSettings.getObject("rawSettings"))?.also { tcpSettings -> tcpSettings.getObject("header")?.also { header -> header.getString("type")?.lowercase()?.also { type -> when (type) { "none" -> {} "http" -> { v2rayBean.headerType = "http" header.getObject("request")?.also { request -> request.getStringArray("path")?.also { v2rayBean.path = it.joinToString("\n") } ?: request.getString("path")?.also { v2rayBean.path = it.split(",").joinToString("\n") } request.getObject("headers")?.also { headers -> headers.getStringArray("Host")?.also { v2rayBean.host = it.joinToString("\n") } ?: headers.getString("Host")?.also { v2rayBean.host = it.split(",").joinToString("\n") } } } } else -> return listOf() } } } } } "kcp", "mkcp" -> { v2rayBean.type = "kcp" streamSettings.getObject("kcpSettings")?.also { kcpSettings -> kcpSettings.getString("seed")?.also { v2rayBean.mKcpSeed = it } kcpSettings.getObject("header")?.also { header -> header.getString("type")?.lowercase()?.also { if (it !in supportedKcpQuicHeaderType) return listOf() v2rayBean.headerType = it } } } streamSettings.getObject("finalmask")?.also { finalmask -> // fuck RPRX finalmask.getArray("udp")?.takeIf { it.isNotEmpty() }?.also { udpMasks -> if (udpMasks.size !in 1..2) return listOf() var isMkcpLegacy = false when (udpMasks.last().getString("type")) { "mkcp-original" -> {} "mkcp-aes128gcm" -> { udpMasks.last().getObject("settings")?.also { settings -> settings.getString("password").orEmpty().also { if (it.isEmpty()) return listOf() v2rayBean.mKcpSeed = it } } } "mkcp-legacy" -> { isMkcpLegacy = true udpMasks.last().getObject("settings")?.also { settings -> settings.getString("header").orEmpty().lowercase().also { when (it) { "dtls", "srtp", "utp", "wireguard" -> v2rayBean.headerType = it "wechat" -> v2rayBean.headerType = "wechat-video" else -> return listOf() } } } } else -> return listOf() } if (udpMasks.size == 2) { when (val type = udpMasks.first().getString("type")) { null -> {} "header-wechat" -> { if (isMkcpLegacy) return listOf() v2rayBean.headerType = "wechat-video" } "header-dtls", "header-srtp", "header-utp", "header-wireguard" -> { if (isMkcpLegacy) return listOf() v2rayBean.headerType = type.removePrefix("header-") } "mkcp-legacy" -> { if (!isMkcpLegacy) return listOf() udpMasks.first().getObject("settings")?.also { settings -> settings.getString("header").orEmpty().also { if (it.isNotEmpty()) return listOf() } settings.getString("value").orEmpty().also { v2rayBean.mKcpSeed = it } } } else -> return listOf() } } } } } "ws", "websocket" -> { v2rayBean.type = "ws" // Fuck Xray ws ALPN // https://github.com/XTLS/Xray-core/blob/1bdb488c9ec09ea51e6899697d5b7437f3cf6eb2/transport/internet/tls/tls.go#L94-L131 v2rayBean.alpn = null streamSettings.getObject("wsSettings")?.also { wsSettings -> wsSettings.getObject("headers")?.also { v2rayBean.host = it.getString("host") } wsSettings.getString("host")?.also { // Xray has a separate field of Host header // will not follow the breaking change in // https://github.com/XTLS/Xray-core/commit/a2b773135a860f63e990874c551b099dfc888471 v2rayBean.host = it } wsSettings.getInt("maxEarlyData")?.also { v2rayBean.maxEarlyData = it } wsSettings.getString("earlyDataHeaderName")?.also { v2rayBean.earlyDataHeaderName = it } wsSettings.getString("path")?.also { path -> v2rayBean.path = path try { // RPRX's smart-assed invention. This of course will break under some conditions. val u = Libexclavecore.parseURL(path) u.queryParameter("ed")?.also { ed -> u.deleteQueryParameter("ed") v2rayBean.path = u.string ed.toIntOrNull()?.also { v2rayBean.maxEarlyData = it } v2rayBean.earlyDataHeaderName = "Sec-WebSocket-Protocol" } } catch (_: Exception) {} } } } "http", "h2" -> { v2rayBean.type = "http" streamSettings.getObject("httpSettings")?.also { httpSettings -> // will not follow the breaking change in // https://github.com/XTLS/Xray-core/commit/0a252ac15d34e7c23a1d3807a89bfca51cbb559b httpSettings.getStringArray("host")?.also { v2rayBean.host = it.joinToString("\n") } ?: httpSettings.getString("host")?.also { v2rayBean.host = it.split(",").joinToString("\n") } httpSettings.getString("path")?.also { v2rayBean.path = it } } } "quic" -> { v2rayBean.type = "quic" streamSettings.getObject("quicSettings")?.also { quicSettings -> quicSettings.getString("security")?.lowercase()?.also { if (it !in supportedQuicSecurity) return listOf() v2rayBean.quicSecurity = it } quicSettings.getString("key")?.also { v2rayBean.quicKey = it } quicSettings.getObject("header")?.also { header -> header.getString("type")?.lowercase()?.also { if (it !in supportedKcpQuicHeaderType) return listOf() v2rayBean.headerType = it } } } } "grpc", "gun" -> { v2rayBean.type = "grpc" // Xray hijacks the share link standard, uses escaped `serviceName` and some other non-standard `serviceName`s and breaks the compatibility with other implementations. // Fixing the compatibility with Xray will break the compatibility with V2Ray and others. // So do not fix the compatibility with Xray. (streamSettings.getObject("grpcSettings") ?: streamSettings.getObject("gunSettings"))?.also { grpcSettings -> grpcSettings.getString("serviceName")?.also { v2rayBean.grpcServiceName = it } grpcSettings.getBoolean("multiMode")?.also { v2rayBean.grpcMultiMode = it // Xray private } } } "httpupgrade" -> { v2rayBean.type = "httpupgrade" // Fuck Xray httpupgrade ALPN // https://github.com/XTLS/Xray-core/blob/1bdb488c9ec09ea51e6899697d5b7437f3cf6eb2/transport/internet/tls/tls.go#L94-L131 v2rayBean.alpn = null streamSettings.getObject("httpupgradeSettings")?.also { httpupgradeSettings -> httpupgradeSettings.getString("host")?.also { // will not follow the breaking change in // https://github.com/XTLS/Xray-core/commit/a2b773135a860f63e990874c551b099dfc888471 v2rayBean.host = it } httpupgradeSettings.getString("path")?.also { v2rayBean.path = it try { // RPRX's smart-assed invention. This of course will break under some conditions. val u = Libexclavecore.parseURL(it) u.queryParameter("ed")?.also { u.deleteQueryParameter("ed") v2rayBean.path = u.string } } catch (_: Exception) {} } httpupgradeSettings.getInt("maxEarlyData")?.also { v2rayBean.maxEarlyData = it } httpupgradeSettings.getString("earlyDataHeaderName")?.also { v2rayBean.earlyDataHeaderName = it } } } "meek" -> { v2rayBean.type = "meek" streamSettings.getObject("meekSettings")?.also { meekSettings -> meekSettings.getString("url")?.also { v2rayBean.meekUrl = it } } } "mekya" -> { v2rayBean.type = "mekya" streamSettings.getObject("mekyaSettings")?.also { mekyaSettings -> mekyaSettings.getString("url")?.also { v2rayBean.mekyaUrl = it } mekyaSettings.getObject("kcp")?.also { kcp -> kcp.getString("seed")?.also { v2rayBean.mekyaKcpSeed = it } kcp.getObject("header")?.also { header -> header.getString("type")?.lowercase()?.also { if (it !in supportedKcpQuicHeaderType) return listOf() v2rayBean.mekyaKcpHeaderType = it } } } } } "splithttp", "xhttp" -> { v2rayBean.type = "splithttp" (streamSettings.getObject("splithttpSettings") ?: streamSettings.getObject("xhttpSettings"))?.also { splithttpSettings -> splithttpSettings.getString("host")?.also { v2rayBean.host = it } splithttpSettings.getString("path")?.also { v2rayBean.path = it } splithttpSettings.getString("mode")?.also { v2rayBean.splithttpMode = when (it) { in supportedXhttpMode -> it "" -> "auto" else -> return listOf() } } // fuck RPRX `extra` var extra = JsonObject() splithttpSettings.getObject("extra")?.also { extra = it } if (!extra.contains("scMaxEachPostBytes")) { splithttpSettings.getInt("scMaxEachPostBytes")?.also { extra.addProperty("scMaxEachPostBytes", it) } ?: splithttpSettings.getString("scMaxEachPostBytes")?.also { extra.addProperty("scMaxEachPostBytes", it) } } if (!extra.contains("scMinPostsIntervalMs")) { splithttpSettings.getInt("scMinPostsIntervalMs")?.also { extra.addProperty("scMinPostsIntervalMs", it) } ?: splithttpSettings.getString("scMinPostsIntervalMs")?.also { extra.addProperty("scMinPostsIntervalMs", it) } } if (!extra.contains("xPaddingBytes")) { splithttpSettings.getInt("xPaddingBytes")?.also { extra.addProperty("xPaddingBytes", it) } ?: splithttpSettings.getString("xPaddingBytes")?.also { extra.addProperty("xPaddingBytes", it) } } if (!extra.contains("noGRPCHeader")) { splithttpSettings.getBoolean("noGRPCHeader")?.also { extra.addProperty("noGRPCHeader", it) } } if (!extra.contains("xmux")) { splithttpSettings.getObject("xmux")?.also { extra.add("xmux", it) } } if (!extra.contains("downloadSettings")) { splithttpSettings.getObject("downloadSettings")?.also { extra.add("downloadSettings", it) } } if (!extra.contains("xPaddingObfsMode")) { splithttpSettings.getBoolean("xPaddingObfsMode")?.also { extra.addProperty("xPaddingObfsMode", it) } } if (!extra.contains("xPaddingKey")) { splithttpSettings.getString("xPaddingKey")?.also { extra.addProperty("xPaddingKey", it) } } if (!extra.contains("xPaddingHeader")) { splithttpSettings.getString("xPaddingHeader")?.also { extra.addProperty("xPaddingHeader", it) } } if (!extra.contains("xPaddingPlacement")) { splithttpSettings.getString("xPaddingPlacement")?.also { extra.addProperty("xPaddingPlacement", it) } } if (!extra.contains("xPaddingMethod")) { splithttpSettings.getString("xPaddingMethod")?.also { extra.addProperty("xPaddingMethod", it) } } if (!extra.contains("uplinkHTTPMethod")) { splithttpSettings.getString("uplinkHTTPMethod")?.also { extra.addProperty("uplinkHTTPMethod", it) } } if (!extra.contains("sessionIDPlacement")) { splithttpSettings.getString("sessionIDPlacement")?.also { extra.addProperty("sessionIDPlacement", it) } } if (!extra.contains("sessionIDKey")) { splithttpSettings.getString("sessionIDKey")?.also { extra.addProperty("sessionIDKey", it) } } if (!extra.contains("sessionIDTable")) { splithttpSettings.getString("sessionIDTable")?.also { extra.addProperty("sessionIDTable", it) } } if (!extra.contains("sessionIDLength")) { splithttpSettings.getInt("sessionIDLength")?.also { extra.addProperty("sessionIDLength", it) } ?: splithttpSettings.getString("sessionIDLength")?.also { extra.addProperty("sessionIDLength", it) } } if (!extra.contains("seqPlacement")) { splithttpSettings.getString("seqPlacement")?.also { extra.addProperty("seqPlacement", it) } } if (!extra.contains("seqKey")) { splithttpSettings.getString("seqKey")?.also { extra.addProperty("seqKey", it) } } if (!extra.contains("uplinkDataPlacement")) { splithttpSettings.getString("uplinkDataPlacement")?.also { extra.addProperty("uplinkDataPlacement", it) } } if (!extra.contains("uplinkDataKey")) { splithttpSettings.getString("uplinkDataKey")?.also { extra.addProperty("uplinkDataKey", it) } } if (!extra.contains("uplinkChunkSize")) { splithttpSettings.getInt("uplinkChunkSize")?.also { extra.addProperty("uplinkChunkSize", it) } ?: splithttpSettings.getString("uplinkChunkSize")?.also { extra.addProperty("uplinkChunkSize", it) } } if (!extra.isEmpty) { v2rayBean.splithttpExtra = GsonBuilder().setPrettyPrinting().create().toJson(extra) } } streamSettings.getObject("finalmask")?.also { finalmask -> if (v2rayBean.alpn != "h3") { // ban Xray TCP finalmask finalmask.getArray("tcp")?.takeIf { it.isNotEmpty() }?.also { return listOf() } } else { // ban Xray UDP finalmask finalmask.getArray("udp")?.takeIf { it.isNotEmpty() }?.also { return listOf() } // ban Xray QUIC port hopping finalmask.getObject("quicParams")?.also { quicParams -> quicParams.getObject("udphop")?.also { udphop -> udphop.getInt("ports")?.also { return listOf() } ?: udphop.getString("ports")?.takeIf { it.isNotEmpty() }?.also { it.split(",").joinToString(",") { it.trim() } .takeIf { it.isValidHysteriaPort(disallowFromGreaterThanTo = true) } ?.also { return listOf() } } } } } } } "hysteria2", "hy2" -> { v2rayBean.type = "hysteria2" streamSettings.getObject("hy2Settings")?.also { hy2Settings -> hy2Settings.getString("password")?.also { v2rayBean.hy2Password = it } hy2Settings.getObject("obfs")?.also { obfs -> obfs.getString("type")?.also { type -> if (type.isNotEmpty()) { return listOf() } } } } } "hysteria" -> { // Xray hysteria2 transport streamSettings.getObject("hysteriaSettings")?.also { hysteriaSettings -> if (hysteriaSettings.getInt("version") != 2) { return listOf() } hysteriaSettings.getString("auth")?.also { v2rayBean.hy2Password = it } } streamSettings.getObject("finalmask")?.also { finalmask -> // ban Xray UDP finalmask finalmask.getArray("udp")?.takeIf { it.isNotEmpty() }?.also { return listOf() } // ban Xray QUIC port hopping finalmask.getObject("quicParams")?.also { quicParams -> quicParams.getObject("udphop")?.also { udphop -> udphop.getInt("ports")?.also { return listOf() } ?: udphop.getString("ports")?.takeIf { it.isNotEmpty() }?.also { it.split(",").joinToString(",") { it.trim() } .takeIf { it.isValidHysteriaPort(disallowFromGreaterThanTo = true) } ?.also { return listOf() } } } } } } else -> return listOf() } when (v2rayBean.type) { "ws", "grpc", "httpupgrade" -> { streamSettings.getObject("finalmask")?.also { finalmask -> // ban Xray TCP finalmask finalmask.getArray("tcp")?.takeIf { it.isNotEmpty() }?.also { return listOf() } } } } } } when (proto) { "vmess" -> { v2rayBean as VMessBean (outbound.getString("tag"))?.also { v2rayBean.name = it } outbound.getObject("settings")?.also { settings -> v2rayBean.packetEncoding = when (settings.getString("packetEncoding")?.lowercase()) { "xudp" -> "xudp" "packet" -> "packet" else -> "none" } settings.getString("address")?.also { address -> v2rayBean.serverAddress = address settings.getPort("port")?.also { v2rayBean.serverPort = it } ?: return listOf() settings.getString("id")?.also { v2rayBean.uuid = uuidOrGenerate(it) } settings.getString("security")?.lowercase()?.also { if (it !in supportedVmessMethod) return listOf() v2rayBean.encryption = it } settings.getInt("alterId")?.also { v2rayBean.alterId = it } settings.getString("experiments")?.also { if (it.contains("AuthenticatedLength")) { v2rayBean.experimentalAuthenticatedLength = true } if (it.contains("NoTerminationSignal")) { v2rayBean.experimentalNoTerminationSignal = true } } } ?: settings.getArray("vnext")?.get(0)?.also { vnext -> vnext.getString("address")?.also { v2rayBean.serverAddress = it } ?: return listOf() vnext.getPort("port")?.also { v2rayBean.serverPort = it } ?: return listOf() vnext.getArray("users")?.get(0)?.also { user -> user.getString("id")?.also { v2rayBean.uuid = uuidOrGenerate(it) } user.getString("security")?.lowercase()?.also { if (it !in supportedVmessMethod) return listOf() v2rayBean.encryption = it } user.getInt("alterId")?.also { v2rayBean.alterId = it } user.getString("experiments")?.also { if (it.contains("AuthenticatedLength")) { v2rayBean.experimentalAuthenticatedLength = true } if (it.contains("NoTerminationSignal")) { v2rayBean.experimentalNoTerminationSignal = true } } } } } } "vless" -> { v2rayBean as VLESSBean (outbound.getString("tag"))?.also { v2rayBean.name = it } outbound.getObject("settings")?.also { settings -> v2rayBean.packetEncoding = when (settings.getString("packetEncoding")?.lowercase()) { "xudp" -> "xudp" "packet" -> "packet" else -> "none" } settings.getString("address")?.also { address -> settings.getString("reverse")?.also { return listOf() } v2rayBean.serverAddress = address settings.getPort("port")?.also { v2rayBean.serverPort = it } ?: return listOf() settings.getString("id")?.also { v2rayBean.uuid = uuidOrGenerate(it) } settings.getString("flow")?.also { when (it) { in supportedVlessFlow -> { v2rayBean.flow = "xtls-rprx-vision-udp443" v2rayBean.packetEncoding = "xudp" } in legacyVlessFlow, "", "none" -> {} else -> if (it.startsWith("xtls-rprx-")) return listOf() } } when (val encryption = settings.getString("encryption")) { "none" -> v2rayBean.encryption = "none" "", null -> return listOf() else -> { val parts = encryption.split(".") if (parts.size < 4 || parts[0] != "mlkem768x25519plus" || !(parts[1] == "native" || parts[1] == "xorpub" || parts[1] != "random") || !(parts[2] == "1rtt" || parts[2] == "0rtt")) { error("unsupported vless encryption") } v2rayBean.encryption = encryption } } } ?: settings.getArray("vnext")?.get(0)?.also { vnext -> vnext.getString("reverse")?.also { return listOf() } vnext.getString("address")?.also { v2rayBean.serverAddress = it } ?: return listOf() vnext.getPort("port")?.also { v2rayBean.serverPort = it } ?: return listOf() vnext.getArray("users")?.get(0)?.also { user -> user.getString("id")?.also { v2rayBean.uuid = uuidOrGenerate(it) } user.getString("flow")?.also { when (it) { in supportedVlessFlow -> { v2rayBean.flow = "xtls-rprx-vision-udp443" v2rayBean.packetEncoding = "xudp" } in legacyVlessFlow, "", "none" -> {} else -> if (it.startsWith("xtls-rprx-")) return listOf() } } when (val encryption = user.getString("encryption")) { "none" -> v2rayBean.encryption = "none" "", null -> return listOf() else -> { val parts = encryption.split(".") if (parts.size < 4 || parts[0] != "mlkem768x25519plus" || !(parts[1] == "native" || parts[1] == "xorpub" || parts[1] != "random") || !(parts[2] == "1rtt" || parts[2] == "0rtt")) { error("unsupported vless encryption") } v2rayBean.encryption = encryption } } } } } } "shadowsocks" -> { v2rayBean as ShadowsocksBean outbound.getString("tag")?.also { v2rayBean.name = it } outbound.getObject("settings")?.also { settings -> settings.getString("address")?.also { address -> v2rayBean.serverAddress = address settings.getPort("port")?.also { v2rayBean.serverPort = it } ?: return listOf() settings.getString("method")?.lowercase()?.also { v2rayBean.method = when (it) { in supportedShadowsocksMethod -> it "aes_128_gcm", "aead_aes_128_gcm" -> "aes-128-gcm" "aes_192_gcm", "aead_aes_192_gcm" -> "aes-192-gcm" "aes_256_gcm", "aead_aes_256_gcm" -> "aes-256-gcm" "chacha20_poly1305", "aead_chacha20_poly1305", "chacha20-poly1305" -> "chacha20-ietf-poly1305" "xchacha20_poly1305", "aead_xchacha20_poly1305", "xchacha20-poly1305" -> "xchacha20-ietf-poly1305" "plain" -> "none" else -> return listOf() } } settings.getString("password")?.also { v2rayBean.password = it } settings.getString("plugin")?.also { pluginId -> v2rayBean.plugin = PluginOptions(pluginId, settings.getString("pluginOpts")).toString(trimId = false) } } ?: settings.getArray("servers")?.get(0)?.also { server -> settings.getString("plugin")?.also { pluginId -> v2rayBean.plugin = PluginOptions(pluginId, settings.getString("pluginOpts")).toString(trimId = false) } server.getString("address")?.also { v2rayBean.serverAddress = it } ?: return listOf() server.getPort("port")?.also { v2rayBean.serverPort = it } ?: return listOf() server.getString("method")?.lowercase()?.also { v2rayBean.method = when (it) { in supportedShadowsocksMethod -> it "aes_128_gcm", "aead_aes_128_gcm" -> "aes-128-gcm" "aes_192_gcm", "aead_aes_192_gcm" -> "aes-192-gcm" "aes_256_gcm", "aead_aes_256_gcm" -> "aes-256-gcm" "chacha20_poly1305", "aead_chacha20_poly1305", "chacha20-poly1305" -> "chacha20-ietf-poly1305" "xchacha20_poly1305", "aead_xchacha20_poly1305", "xchacha20-poly1305" -> "xchacha20-ietf-poly1305" "plain" -> "none" else -> return listOf() } } server.getString("password")?.also { v2rayBean.password = it } } } if (v2rayBean.plugin == "shadowsocksr") { return listOf() } } "shadowsocks2022" -> { v2rayBean as ShadowsocksBean outbound.getString("tag")?.also { v2rayBean.name = it } outbound.getObject("settings")?.also { settings -> settings.getString("address")?.also { v2rayBean.serverAddress = it } ?: return listOf() settings.getPort("port")?.also { v2rayBean.serverPort = it } ?: return listOf() settings.getString("method")?.lowercase()?.also { if (it !in supportedShadowsocks2022Method) return listOf() v2rayBean.method = it } settings.getString("psk")?.also { psk -> v2rayBean.password = psk (settings.getStringArray("ipsk"))?.also { ipsk -> v2rayBean.password = ipsk.joinToString(":") + ":" + psk } } settings.getString("plugin")?.also { pluginId -> v2rayBean.plugin = PluginOptions(pluginId, settings.getString("pluginOpts")).toString(trimId = false) } } } "shadowsocks-2022" -> { v2rayBean as ShadowsocksBean outbound.getString("tag")?.also { v2rayBean.name = it } outbound.getObject("settings")?.also { settings -> settings.getString("address")?.also { v2rayBean.serverAddress = it } ?: return listOf() settings.getPort("port")?.also { v2rayBean.serverPort = it } ?: return listOf() settings.getString("method")?.lowercase()?.also { if (it !in supportedShadowsocks2022Method) return listOf() v2rayBean.method = it } settings.getString("password")?.also { v2rayBean.password = it } settings.getString("plugin")?.also { pluginId -> v2rayBean.plugin = PluginOptions(pluginId, settings.getString("pluginOpts")).toString(trimId = false) } } } "trojan" -> { v2rayBean as TrojanBean outbound.getString("tag")?.also { v2rayBean.name = it } outbound.getObject("settings")?.also { settings -> settings.getString("address")?.also { address -> v2rayBean.serverAddress = address settings.getPort("port")?.also { v2rayBean.serverPort = it } ?: return listOf() settings.getString("password")?.also { v2rayBean.password = it } } ?: settings.getArray("servers")?.get(0)?.also { server -> server.getString("address")?.also { v2rayBean.serverAddress = it } ?: return listOf() server.getPort("port")?.also { v2rayBean.serverPort = it } ?: return listOf() server.getString("password")?.also { v2rayBean.password = it } } } } "socks" -> { v2rayBean as SOCKSBean outbound.getString("tag")?.also { v2rayBean.name = it } outbound.getObject("settings")?.also { settings -> v2rayBean.protocol = when (settings.getString("version")?.lowercase()) { "4" -> SOCKSBean.PROTOCOL_SOCKS4 "4a" -> SOCKSBean.PROTOCOL_SOCKS4A "", "5" -> SOCKSBean.PROTOCOL_SOCKS5 else -> return listOf() } settings.getString("address")?.also { address -> // Xray hack v2rayBean.serverAddress = address settings.getPort("port")?.also { v2rayBean.serverPort = it } ?: return listOf() settings.getString("user")?.also { v2rayBean.username = it } if (v2rayBean.protocol == SOCKSBean.PROTOCOL_SOCKS5) { settings.getString("pass")?.also { v2rayBean.password = it } } } ?: settings.getArray("servers")?.get(0)?.also { server -> server.getString("address")?.also { v2rayBean.serverAddress = it } ?: return listOf() server.getPort("port")?.also { v2rayBean.serverPort = it } ?: return listOf() server.getArray("users")?.get(0)?.also { user -> user.getString("user")?.also { v2rayBean.username = it } if (v2rayBean.protocol == SOCKSBean.PROTOCOL_SOCKS5) { settings.getString("pass")?.also { v2rayBean.password = it } } } } } } "http" -> { v2rayBean as HttpBean outbound.getString("tag")?.also { v2rayBean.name = it } outbound.getObject("settings")?.also { settings -> settings.getString("address")?.also { address -> // Xray hack v2rayBean.serverAddress = address settings.getPort("port")?.also { v2rayBean.serverPort = it } ?: return listOf() settings.getString("user")?.also { v2rayBean.username = it } settings.getString("pass")?.also { v2rayBean.password = it } } ?: settings.getArray("servers")?.get(0)?.also { server -> server.getString("address")?.also { v2rayBean.serverAddress = it } ?: return listOf() server.getPort("port")?.also { v2rayBean.serverPort = it } ?: return listOf() server.getArray("users")?.get(0)?.also { user -> user.getString("user")?.also { v2rayBean.username = it } user.getString("pass")?.also { v2rayBean.password = it } } } } } } return listOf(v2rayBean) } "hysteria2" -> { val hysteria2Bean = Hysteria2Bean() outbound.getString("tag")?.also { hysteria2Bean.name = it } outbound.getObject("settings")?.also { settings -> settings.getString("address")?.also { address -> hysteria2Bean.serverAddress = address settings.getPort("port")?.also { hysteria2Bean.serverPorts = it.toString() } ?: return listOf() } ?: settings.getArray("servers")?.get(0)?.also { server -> server.getString("address")?.also { hysteria2Bean.serverAddress = it } ?: return listOf() server.getPort("port")?.also { hysteria2Bean.serverPorts = it.toString() } ?: return listOf() } } outbound.getObject("streamSettings")?.also { streamSettings -> streamSettings.getString("network")?.lowercase()?.also { network -> when (network) { "hysteria2", "hy2" -> { streamSettings.getObject("hy2Settings")?.also { hy2Settings -> hy2Settings.getString("password")?.also { hysteria2Bean.auth = it } hy2Settings.getObject("obfs")?.also { obfs -> obfs.getString("type")?.also { type -> when (type) { "" -> {} "salamander" -> { obfs.getString("password")?.also { hysteria2Bean.obfsPassword = it } } "gecko" -> { obfs.getString("password")?.also { hysteria2Bean.obfsPassword = it } obfs.getInt("minPacketSize")?.takeIf { it > 0 }?.also { hysteria2Bean.geckoMinPacketSize = it } obfs.getInt("maxPacketSize")?.takeIf { it > 0 }?.also { hysteria2Bean.geckoMaxPacketSize = it } } else -> return listOf() } } } hy2Settings.getString("hopPorts")?.takeIf { it.isValidHysteriaMultiPort() }?.also { hysteria2Bean.serverPorts = it } hy2Settings.getLong("hopInterval")?.also { hysteria2Bean.hopInterval = it.takeIf { it > 0 } } if (hysteria2Bean.hopInterval == null) { hy2Settings.getLong("hopIntervalMin")?.also { hysteria2Bean.hopIntervalMin = it.takeIf { it > 0 } } hy2Settings.getLong("hopIntervalMax")?.also { hysteria2Bean.hopIntervalMax = it.takeIf { it > 0 } } } } } else -> return listOf() } } streamSettings.getString("security")?.lowercase()?.also { security -> when (security) { "tls" -> { streamSettings.getObject("tlsSettings")?.also { tlsSettings -> tlsSettings.getString("serverName")?.also { hysteria2Bean.sni = it } tlsSettings.getBoolean("allowInsecure")?.also { hysteria2Bean.allowInsecure = it } tlsSettings.getArray("certificates")?.asReversed()?.forEach { certificate -> when (certificate.getString("usage")?.lowercase()) { null, "", "encipherment" -> { if (!certificate.contains("certificateFile") && !certificate.contains("keyFile")) { val cert = certificate.getStringArray("certificate")?.joinToString("\n")?.takeIf { it.contains("-----BEGIN ") && it.contains("-----END ") && it.contains(" CERTIFICATE-----") } val key = certificate.getStringArray("key")?.joinToString("\n")?.takeIf { it.contains("-----BEGIN ") && it.contains("-----END ") && it.contains(" PRIVATE KEY-----") } if (cert != null && key != null) { hysteria2Bean.mtlsCertificate = cert hysteria2Bean.mtlsCertificatePrivateKey = key } } } "verify" -> { if (!certificate.contains("certificateFile")) { val cert = certificate.getStringArray("certificate")?.joinToString("\n")?.takeIf { it.contains("-----BEGIN ") && it.contains("-----END ") && it.contains(" CERTIFICATE-----") } if (cert != null) { hysteria2Bean.certificates = cert } } } } } tlsSettings.getStringArray("pinnedPeerCertificateChainSha256")?.also { hysteria2Bean.pinnedPeerCertificateChainSha256 = it.joinToString("\n") tlsSettings.getBoolean("allowInsecureIfPinnedPeerCertificate")?.also { allowInsecure -> hysteria2Bean.allowInsecure = allowInsecure } } tlsSettings.getStringArray("pinnedPeerCertificatePublicKeySha256")?.also { hysteria2Bean.pinnedPeerCertificatePublicKeySha256 = it.joinToString("\n") tlsSettings.getBoolean("allowInsecureIfPinnedPeerCertificate")?.also { allowInsecure -> hysteria2Bean.allowInsecure = allowInsecure } } tlsSettings.getStringArray("pinnedPeerCertificateSha256")?.also { hysteria2Bean.pinnedPeerCertificateSha256 = it.joinToString("\n") tlsSettings.getBoolean("allowInsecureIfPinnedPeerCertificate")?.also { allowInsecure -> hysteria2Bean.allowInsecure = allowInsecure } } tlsSettings.getStringArray("serverNameToVerify")?.also { hysteria2Bean.serverNameToVerify = it.joinToString("\n") } tlsSettings.getString("echDohServer")?.also { hysteria2Bean.echEnabled = true } tlsSettings.getString("echConfig")?.also { hysteria2Bean.echEnabled = true hysteria2Bean.echConfig = it } tlsSettings.getString("echConfigList")?.also { hysteria2Bean.echEnabled = true try { Base64.getDecoder().decode(it) hysteria2Bean.echConfig = it } catch (_: Exception) {} } tlsSettings.getObject("ech")?.also { hysteria2Bean.echEnabled = it.getBoolean("enabled") hysteria2Bean.echConfig = it.getString("config") } } } else -> return listOf() } } } return listOf(hysteria2Bean) } "ssh" -> { outbound.getObject("streamSettings")?.also { streamSettings -> streamSettings.getString("network")?.lowercase()?.also { if (it in nonRawTransportName) return listOf() } streamSettings.getString("security")?.lowercase()?.also { if (it != "none") return listOf() } } val sshBean = SSHBean() outbound.getObject("settings")?.also { settings -> outbound.getString("tag")?.also { sshBean.name = it } settings.getString("address")?.also { sshBean.serverAddress = it } ?: return listOf() settings.getPort("port")?.also { sshBean.serverPort = it } ?: return listOf() settings.getString("user")?.also { sshBean.username = it } settings.getString("publicKey")?.also { sshBean.publicKey = it } settings.getString("password")?.also { sshBean.authType = SSHBean.AUTH_TYPE_PASSWORD sshBean.password = it } settings.getString("privateKey")?.also { sshBean.authType = SSHBean.AUTH_TYPE_PUBLIC_KEY sshBean.privateKey = it settings.getString("privateKeyPassphrase")?.also { privateKeyPassphrase -> sshBean.privateKeyPassphrase = privateKeyPassphrase } } } return listOf(sshBean) } "tuic" -> { val tuic5Bean = Tuic5Bean() var tlsSettingsObject: JsonObject? = null outbound.getObject("streamSettings")?.also { streamSettings -> if (streamSettings.getString("security") != "tls") return listOf() streamSettings.getObject("tlsSettings")?.also { tlsSettingsObject = it } } outbound.getObject("settings")?.also { settings -> outbound.getString("tag")?.also { tuic5Bean.name = it } settings.getString("address")?.also { tuic5Bean.serverAddress = it } ?: return listOf() settings.getPort("port")?.also { tuic5Bean.serverPort = it } ?: return listOf() settings.getString("uuid")?.also { tuic5Bean.uuid = it } settings.getString("password")?.also { tuic5Bean.password = it } settings.getString("congestionControl")?.also { tuic5Bean.congestionControl = if (it in supportedTuic5CongestionControl) it else "cubic" } settings.getString("udpRelayMode")?.also { tuic5Bean.udpRelayMode = if (it in supportedTuic5RelayMode) it else "native" } settings.getBoolean("zeroRTTHandshake")?.also { tuic5Bean.zeroRTTHandshake = it } settings.getObject("tlsSettings")?.also { // old version of Owenclave compatibility tlsSettingsObject = it } settings.getBoolean("disableSNI")?.also { tuic5Bean.disableSNI = it } } tlsSettingsObject?.also { tlsSettings -> tlsSettings.getString("serverName")?.also { tuic5Bean.sni = it } tlsSettings.getBoolean("allowInsecure")?.also { tuic5Bean.allowInsecure = it } tlsSettings.getStringArray("alpn")?.also { tuic5Bean.alpn = it.joinToString("\n") } ?: tlsSettings.getString("alpn")?.also { tuic5Bean.alpn = it.split(",").joinToString("\n") } tlsSettings.getArray("certificates")?.asReversed()?.forEach { certificate -> when (certificate.getString("usage")?.lowercase()) { null, "", "encipherment" -> { if (!certificate.contains("certificateFile") && !certificate.contains("keyFile")) { val cert = certificate.getStringArray("certificate")?.joinToString("\n")?.takeIf { it.contains("-----BEGIN ") && it.contains("-----END ") && it.contains(" CERTIFICATE-----") } val key = certificate.getStringArray("key")?.joinToString("\n")?.takeIf { it.contains("-----BEGIN ") && it.contains("-----END ") && it.contains(" PRIVATE KEY-----") } if (cert != null && key != null) { tuic5Bean.mtlsCertificate = cert tuic5Bean.mtlsCertificatePrivateKey = key } } } "verify" -> { if (!certificate.contains("certificateFile")) { val cert = certificate.getStringArray("certificate")?.joinToString("\n")?.takeIf { it.contains("-----BEGIN ") && it.contains("-----END ") && it.contains(" CERTIFICATE-----") } if (cert != null) { tuic5Bean.certificates = cert } } } } } tlsSettings.getStringArray("pinnedPeerCertificateChainSha256")?.also { tuic5Bean.pinnedPeerCertificateChainSha256 = it.joinToString("\n") tlsSettings.getBoolean("allowInsecureIfPinnedPeerCertificate")?.also { allowInsecure -> tuic5Bean.allowInsecure = allowInsecure } } tlsSettings.getStringArray("pinnedPeerCertificatePublicKeySha256")?.also { tuic5Bean.pinnedPeerCertificatePublicKeySha256 = it.joinToString("\n") tlsSettings.getBoolean("allowInsecureIfPinnedPeerCertificate")?.also { allowInsecure -> tuic5Bean.allowInsecure = allowInsecure } } tlsSettings.getStringArray("pinnedPeerCertificateSha256")?.also { tuic5Bean.pinnedPeerCertificateSha256 = it.joinToString("\n") tlsSettings.getBoolean("allowInsecureIfPinnedPeerCertificate")?.also { allowInsecure -> tuic5Bean.allowInsecure = allowInsecure } } tlsSettings.getStringArray("serverNameToVerify")?.also { tuic5Bean.serverNameToVerify = it.joinToString("\n") } /*tlsSettings.getObject("ech")?.also { tuic5Bean.echEnabled = it.getBoolean("enabled") tuic5Bean.echConfig = it.getString("config") }*/ } return listOf(tuic5Bean) } "http3" -> { val http3Bean = Http3Bean() var tlsSettingsObject: JsonObject? = null outbound.getObject("streamSettings")?.also { streamSettings -> if (streamSettings.getString("security") != "tls") return listOf() streamSettings.getObject("tlsSettings")?.also { tlsSettingsObject = it } } outbound.getObject("settings")?.also { settings -> outbound.getString("tag")?.also { http3Bean.name = it } settings.getString("address")?.also { http3Bean.serverAddress = it } ?: return listOf() settings.getPort("port")?.also { http3Bean.serverPort = it } ?: return listOf() settings.getString("username")?.also { http3Bean.username = it } settings.getString("password")?.also { http3Bean.password = it } settings.getObject("tlsSettings")?.also { // old version of Owenclave compatibility tlsSettingsObject = it } } tlsSettingsObject?.also { tlsSettings -> tlsSettings.getString("serverName")?.also { http3Bean.sni = it } tlsSettings.getBoolean("allowInsecure")?.also { http3Bean.allowInsecure = it } tlsSettings.getArray("certificates")?.asReversed()?.forEach { certificate -> when (certificate.getString("usage")?.lowercase()) { null, "", "encipherment" -> { if (!certificate.contains("certificateFile") && !certificate.contains("keyFile")) { val cert = certificate.getStringArray("certificate")?.joinToString("\n")?.takeIf { it.contains("-----BEGIN ") && it.contains("-----END ") && it.contains(" CERTIFICATE-----") } val key = certificate.getStringArray("key")?.joinToString("\n")?.takeIf { it.contains("-----BEGIN ") && it.contains("-----END ") && it.contains(" PRIVATE KEY-----") } if (cert != null && key != null) { http3Bean.mtlsCertificate = cert http3Bean.mtlsCertificatePrivateKey = key } } } "verify" -> { if (!certificate.contains("certificateFile")) { val cert = certificate.getStringArray("certificate")?.joinToString("\n")?.takeIf { it.contains("-----BEGIN ") && it.contains("-----END ") && it.contains(" CERTIFICATE-----") } if (cert != null) { http3Bean.certificates = cert } } } } } tlsSettings.getStringArray("pinnedPeerCertificateChainSha256")?.also { http3Bean.pinnedPeerCertificateChainSha256 = it.joinToString("\n") tlsSettings.getBoolean("allowInsecureIfPinnedPeerCertificate")?.also { allowInsecure -> http3Bean.allowInsecure = allowInsecure } } tlsSettings.getStringArray("pinnedPeerCertificatePublicKeySha256")?.also { http3Bean.pinnedPeerCertificatePublicKeySha256 = it.joinToString("\n") tlsSettings.getBoolean("allowInsecureIfPinnedPeerCertificate")?.also { allowInsecure -> http3Bean.allowInsecure = allowInsecure } } tlsSettings.getStringArray("pinnedPeerCertificateSha256")?.also { http3Bean.pinnedPeerCertificateSha256 = it.joinToString("\n") tlsSettings.getBoolean("allowInsecureIfPinnedPeerCertificate")?.also { allowInsecure -> http3Bean.allowInsecure = allowInsecure } } tlsSettings.getStringArray("serverNameToVerify")?.also { http3Bean.serverNameToVerify = it.joinToString("\n") } /*tlsSettings.getObject("ech")?.also { http3Bean.echEnabled = it.getBoolean("enabled") http3Bean.echConfig = it.getString("config") }*/ } return listOf(http3Bean) } "anytls" -> { outbound.getObject("streamSettings")?.also { streamSettings -> streamSettings.getString("network")?.lowercase()?.also { if (it in nonRawTransportName) return listOf() } } val anytlsBean = AnyTLSBean() outbound.getObject("settings")?.also { settings -> outbound.getString("tag")?.also { anytlsBean.name = it } settings.getString("address")?.also { anytlsBean.serverAddress = it } ?: return listOf() settings.getPort("port")?.also { anytlsBean.serverPort = it } ?: return listOf() settings.getString("password")?.also { anytlsBean.password = it } } outbound.getObject("streamSettings")?.also { streamSettings -> when (val security = streamSettings.getString("security")?.lowercase()) { "tls", "utls" -> { anytlsBean.security = "tls" var tlsConfig = streamSettings.getObject("tlsSettings") if (security == "utls") { streamSettings.getObject("utlsSettings")?.also { tlsConfig = it.getObject("tlsConfig") } } tlsConfig?.also { tlsSettings -> tlsSettings.getString("serverName")?.also { anytlsBean.sni = it } tlsSettings.getStringArray("alpn")?.also { anytlsBean.alpn = it.joinToString("\n") } ?: tlsSettings.getString("alpn")?.also { anytlsBean.alpn = it.split(",").joinToString("\n") } tlsSettings.getBoolean("allowInsecure")?.also { anytlsBean.allowInsecure = it } tlsSettings.getArray("certificates")?.asReversed()?.forEach { certificate -> when (certificate.getString("usage")?.lowercase()) { null, "", "encipherment" -> { if (!certificate.contains("certificateFile") && !certificate.contains("keyFile")) { val cert = certificate.getStringArray("certificate")?.joinToString("\n")?.takeIf { it.contains("-----BEGIN ") && it.contains("-----END ") && it.contains(" CERTIFICATE-----") } val key = certificate.getStringArray("key")?.joinToString("\n")?.takeIf { it.contains("-----BEGIN ") && it.contains("-----END ") && it.contains(" PRIVATE KEY-----") } if (cert != null && key != null) { anytlsBean.mtlsCertificate = cert anytlsBean.mtlsCertificatePrivateKey = key } } } "verify" -> { if (!certificate.contains("certificateFile")) { val cert = certificate.getStringArray("certificate")?.joinToString("\n")?.takeIf { it.contains("-----BEGIN ") && it.contains("-----END ") && it.contains(" CERTIFICATE-----") } if (cert != null) { anytlsBean.certificates = cert } } } } } tlsSettings.getStringArray("pinnedPeerCertificateChainSha256")?.also { anytlsBean.pinnedPeerCertificateChainSha256 = it.joinToString("\n") tlsSettings.getBoolean("allowInsecureIfPinnedPeerCertificate")?.also { allowInsecure -> anytlsBean.allowInsecure = allowInsecure } } tlsSettings.getStringArray("pinnedPeerCertificatePublicKeySha256")?.also { anytlsBean.pinnedPeerCertificatePublicKeySha256 = it.joinToString("\n") tlsSettings.getBoolean("allowInsecureIfPinnedPeerCertificate")?.also { allowInsecure -> anytlsBean.allowInsecure = allowInsecure } } tlsSettings.getStringArray("pinnedPeerCertificateSha256")?.also { anytlsBean.pinnedPeerCertificateSha256 = it.joinToString("\n") tlsSettings.getBoolean("allowInsecureIfPinnedPeerCertificate")?.also { allowInsecure -> anytlsBean.allowInsecure = allowInsecure } } tlsSettings.getStringArray("serverNameToVerify")?.also { anytlsBean.serverNameToVerify = it.joinToString("\n") } /*tlsSettings.getObject("ech")?.also { anytlsBean.echEnabled = it.getBoolean("enabled") anytlsBean.echConfig = it.getString("config") }*/ } } "reality" -> { anytlsBean.security = "reality" streamSettings.getObject("realitySettings")?.also { realitySettings -> realitySettings.getString("serverName")?.also { anytlsBean.sni = it } realitySettings.getString("publicKey")?.also { anytlsBean.realityPublicKey = it } realitySettings.getString("shortId")?.also { anytlsBean.realityShortId = it } } } else -> anytlsBean.security = "none" } } return listOf(anytlsBean) } "juicity" -> { val juicityBean = JuicityBean() var tlsSettingsObject: JsonObject? = null outbound.getObject("streamSettings")?.also { streamSettings -> if (streamSettings.getString("security") != "tls") return listOf() streamSettings.getObject("tlsSettings")?.also { tlsSettingsObject = it } } outbound.getObject("settings")?.also { settings -> outbound.getString("tag")?.also { juicityBean.name = it } settings.getString("address")?.also { juicityBean.serverAddress = it } ?: return listOf() settings.getPort("port")?.also { juicityBean.serverPort = it } ?: return listOf() settings.getString("uuid")?.also { juicityBean.uuid = it } settings.getString("password")?.also { juicityBean.password = it } settings.getObject("tlsSettings")?.also { // old version of Owenclave compatibility tlsSettingsObject = it } } tlsSettingsObject?.also { tlsSettings -> tlsSettings.getString("serverName")?.also { juicityBean.sni = it } tlsSettings.getBoolean("allowInsecure")?.also { juicityBean.allowInsecure = it } tlsSettings.getArray("certificates")?.asReversed()?.forEach { certificate -> when (certificate.getString("usage")?.lowercase()) { null, "", "encipherment" -> { if (!certificate.contains("certificateFile") && !certificate.contains("keyFile")) { val cert = certificate.getStringArray("certificate")?.joinToString("\n")?.takeIf { it.contains("-----BEGIN ") && it.contains("-----END ") && it.contains(" CERTIFICATE-----") } val key = certificate.getStringArray("key")?.joinToString("\n")?.takeIf { it.contains("-----BEGIN ") && it.contains("-----END ") && it.contains(" PRIVATE KEY-----") } if (cert != null && key != null) { juicityBean.mtlsCertificate = cert juicityBean.mtlsCertificatePrivateKey = key } } } "verify" -> { if (!certificate.contains("certificateFile")) { val cert = certificate.getStringArray("certificate")?.joinToString("\n")?.takeIf { it.contains("-----BEGIN ") && it.contains("-----END ") && it.contains(" CERTIFICATE-----") } if (cert != null) { juicityBean.certificates = cert } } } } } tlsSettings.getStringArray("pinnedPeerCertificateChainSha256")?.also { juicityBean.pinnedPeerCertificateChainSha256 = it.joinToString("\n") // match Juicity's behavior // https://github.com/juicity/juicity/blob/412dbe43e091788c5464eb2d6e9c169bdf39f19c/cmd/client/run.go#L97 juicityBean.allowInsecure = true } tlsSettings.getStringArray("pinnedPeerCertificatePublicKeySha256")?.also { juicityBean.pinnedPeerCertificatePublicKeySha256 = it.joinToString("\n") tlsSettings.getBoolean("allowInsecureIfPinnedPeerCertificate")?.also { allowInsecure -> juicityBean.allowInsecure = allowInsecure } } tlsSettings.getStringArray("pinnedPeerCertificateSha256")?.also { juicityBean.pinnedPeerCertificateSha256 = it.joinToString("\n") tlsSettings.getBoolean("allowInsecureIfPinnedPeerCertificate")?.also { allowInsecure -> juicityBean.allowInsecure = allowInsecure } } tlsSettings.getStringArray("serverNameToVerify")?.also { juicityBean.serverNameToVerify = it.joinToString("\n") } /*tlsSettings.getObject("ech")?.also { juicityBean.echEnabled = it.getBoolean("enabled") juicityBean.echConfig = it.getString("config") }*/ } return listOf(juicityBean) } "mieru" -> { val mieruBean = MieruBean() outbound.getObject("settings")?.also { settings -> outbound.getString("tag")?.also { mieruBean.name = it } settings.getString("address")?.also { mieruBean.serverAddress = it } ?: return listOf() settings.getPort("port")?.also { mieruBean.serverPort = it } settings.getStringArray("portRange")?.also { mieruBean.portRange = it.joinToString("\n") } if (mieruBean.serverPort == null && mieruBean.portRange == null) { return listOf() } settings.getString("username")?.also { mieruBean.username = it } settings.getString("password")?.also { mieruBean.password = it } settings.getString("protocol")?.also { mieruBean.protocol = when (it) { "tcp", "" -> MieruBean.PROTOCOL_TCP "udp" -> MieruBean.PROTOCOL_UDP else -> return listOf() } } settings.getString("multiplex")?.also { mieruBean.multiplexingLevel = when (it) { "off" -> MieruBean.MULTIPLEXING_OFF "low" -> MieruBean.MULTIPLEXING_LOW "middle" -> MieruBean.MULTIPLEXING_MIDDLE "high" -> MieruBean.MULTIPLEXING_HIGH else -> MieruBean.MULTIPLEXING_DEFAULT } } settings.getString("handshakeMode")?.also { mieruBean.handshakeMode = when (it) { "standard" -> MieruBean.HANDSHAKE_STANDARD "nowait" -> MieruBean.HANDSHAKE_NO_WAIT else -> MieruBean.HANDSHAKE_DEFAULT } } } return listOf(mieruBean) } "snell" -> { val snellBean = SnellBean() outbound.getObject("settings")?.also { settings -> outbound.getString("tag")?.also { snellBean.name = it } settings.getString("address")?.also { snellBean.serverAddress = it } ?: return listOf() settings.getPort("port")?.also { snellBean.serverPort = it } ?: return listOf() snellBean.version = settings.getInt("version") if (snellBean.version != 4 && snellBean.version != 6) { return listOf() } settings.getString("psk")?.also { snellBean.psk = it } if (DataStore.experimentalFlagsProperties.getBooleanProperty("singSnellUserKey")) { settings.getString("userKey")?.also { snellBean.userKey = it } } settings.getBoolean("reuse")?.also { snellBean.reuse = it } when (snellBean.version) { 4 -> when (settings.getString("obfsMode")) { null, "", "none" -> { snellBean.obfsMode = SnellBean.OBFS_NONE } "http" -> { snellBean.obfsMode = SnellBean.OBFS_HTTP snellBean.obfsHost = settings.getString("obfsHost") } "tls" -> { snellBean.obfsMode = SnellBean.OBFS_TLS snellBean.obfsHost = settings.getString("obfsHost") } else -> return listOf() } 6 -> snellBean.mode = when (settings.getString("mode")) { null, "", "default" -> SnellBean.MODE_DEFAULT "unshaped" -> SnellBean.MODE_UNSHAPED "unsafe-raw" -> SnellBean.MODE_UNSAFE_RAW else -> return listOf() } } } return listOf(snellBean) } "wireguard" -> { val beanList = mutableListOf() val wireguardBean = WireGuardBean() outbound.getString("tag")?.also { wireguardBean.name = it } outbound.getObject("settings")?.also { settings -> settings.getString("secretKey")?.also { // https://github.com/XTLS/Xray-core/blob/d8934cf83946e88210b6bb95d793bc06e12b6db8/infra/conf/wireguard.go#L126-L148 if (it.length == 64) { wireguardBean.privateKey = Base64.getEncoder().encodeToString(it.chunked(2).map { it.toInt(16).toByte() }.toByteArray()) } else { wireguardBean.privateKey = it.replace('_', '/').replace('-', '+') if (wireguardBean.privateKey.length == 43) wireguardBean.privateKey += "=" } } // https://github.com/XTLS/Xray-core/blob/d8934cf83946e88210b6bb95d793bc06e12b6db8/infra/conf/wireguard.go#L75 wireguardBean.localAddress = "10.0.0.1/32\nfd59:7153:2388:b5fd:0000:0000:0000:0001/128" settings.getStringArray("address")?.also { wireguardBean.localAddress = it.joinToString("\n") } wireguardBean.mtu = 1420 settings.getInt("mtu")?.takeIf { it > 0 }?.also { wireguardBean.mtu = it } settings.getIntArray("reserved")?.also { if (it.size == 3) { wireguardBean.reserved = listOf(it[0].toString(), it[1].toString(), it[2].toString()).joinToString(",") } } settings.getArray("peers")?.forEach { peer -> beanList.add(wireguardBean.applyDefaultValues().clone().apply { peer.getString("endpoint")?.also { endpoint -> serverAddress = endpoint.substringBeforeLast(":").removePrefix("[").removeSuffix("]") serverPort = endpoint.substringAfterLast(":").toIntOrNull() ?: return listOf() } peer.getString("publicKey")?.also { if (it.length == 64) { peerPublicKey = Base64.getEncoder().encodeToString(it.chunked(2).map { it.toInt(16).toByte() }.toByteArray()) } else { peerPublicKey = it.replace('_', '/').replace('-', '+') if (peerPublicKey.length == 43) peerPublicKey += "=" } } peer.getString("preSharedKey")?.also { if (it.length == 64) { peerPreSharedKey = Base64.getEncoder().encodeToString(it.chunked(2).map { it.toInt(16).toByte() }.toByteArray()) } else { peerPreSharedKey = it.replace('_', '/').replace('-', '+') if (peerPreSharedKey.length == 43) peerPreSharedKey += "=" } } peer.getInt("keepAlive")?.takeIf { it > 0 }?.also { keepaliveInterval = it } }) } } outbound.getObject("streamSettings")?.also { streamSettings -> streamSettings.getObject("finalmask")?.also { finalmask -> // ban Xray UDP finalmask finalmask.getArray("udp")?.takeIf { it.isNotEmpty() }?.also { return listOf() } } } return beanList } "hysteria" -> { // Xray Hysteria 2 val hysteria2Bean = Hysteria2Bean() outbound.getString("tag")?.also { hysteria2Bean.name = it } outbound.getObject("settings")?.also { settings -> when (settings.getInt("version")) { 2 -> {} null, 0 -> {} // xray 26.1.13 else -> return listOf() } settings.getString("address")?.also { hysteria2Bean.serverAddress = it } settings.getPort("port")?.also { hysteria2Bean.serverPorts = it.toString() } ?: return listOf() } outbound.getObject("streamSettings")?.also { streamSettings -> streamSettings.getString("network")?.lowercase()?.also { network -> when (network) { "hysteria" -> { streamSettings.getObject("hysteriaSettings")?.also { hysteriaSettings -> if (hysteriaSettings.getInt("version") != 2) { return listOf() } hysteriaSettings.getString("auth")?.also { hysteria2Bean.auth = it } hysteriaSettings.getObject("udphop")?.also { udphop -> udphop.getInt("port")?.also { if (it > 0) hysteria2Bean.serverPorts = it.toString() } ?: udphop.getString("port")?.takeIf { it.isNotEmpty() }?.also { // invalid port is ignored hysteria2Bean.serverPorts = (it.split(",").joinToString(",") { it.trim() }) .takeIf { it.isValidHysteriaPort(disallowFromGreaterThanTo = true) } } udphop.getLong("interval")?.also { hysteria2Bean.hopInterval = it.takeIf { it > 0 } } ?: udphop.getString("interval")?.also { val intervalLong = it.toLongOrNull() if (intervalLong != null && intervalLong > 0) { hysteria2Bean.hopInterval = intervalLong } else { val intervalStringList = it.split("-") if (intervalStringList.size == 2) { val intervalLong0 = intervalStringList[0].toLongOrNull() val intervalLong1 = intervalStringList[1].toLongOrNull() if (intervalLong0 != null && intervalLong0 > 0 && intervalLong1 != null && intervalLong1 > 0) { hysteria2Bean.hopIntervalMin = minOf(intervalLong0, intervalLong1) hysteria2Bean.hopIntervalMax = maxOf(intervalLong0, intervalLong1) } } } } } } } else -> return listOf() } } streamSettings.getObject("finalmask")?.also { finalmask -> finalmask.getArray("udp")?.takeIf { it.isNotEmpty() }?.also { udpMasks -> if (udpMasks.size != 1) return listOf() val udpmask = udpMasks[0] when (udpmask.getString("type")) { "" -> {} "salamander" -> { hysteria2Bean.obfsType = "salamander" udpmask.getObject("settings")?.also { settings -> settings.getString("password")?.also { hysteria2Bean.obfsPassword = it } } } "gecko" -> { hysteria2Bean.obfsType = "gecko" udpmask.getObject("settings")?.also { settings -> settings.getString("password")?.also { hysteria2Bean.obfsPassword = it } settings.getInt("packetSize")?.also { hysteria2Bean.geckoMinPacketSize = it.takeIf { it > 0 } hysteria2Bean.geckoMaxPacketSize = it.takeIf { it > 0 } } ?: settings.getString("packetSize")?.also { val packetSizeInt = it.toIntOrNull() if (packetSizeInt != null && packetSizeInt > 0) { hysteria2Bean.geckoMinPacketSize = packetSizeInt hysteria2Bean.geckoMaxPacketSize = packetSizeInt } else { val packetSizeStringList = it.split("-") if (packetSizeStringList.size == 2) { val packetSizeInt0 = packetSizeStringList[0].toIntOrNull() val packetSizeInt1 = packetSizeStringList[1].toIntOrNull() if (packetSizeInt0 != null && packetSizeInt0 > 0 && packetSizeInt1 != null && packetSizeInt1 > 0) { hysteria2Bean.geckoMinPacketSize = minOf(packetSizeInt0, packetSizeInt1) hysteria2Bean.geckoMaxPacketSize = maxOf(packetSizeInt0, packetSizeInt1) } } } } } } else -> return listOf() } } finalmask.getObject("quicParams")?.also { quicParams -> quicParams.getObject("udphop")?.also { udphop -> udphop.getInt("ports")?.also { if (it > 0) hysteria2Bean.serverPorts = it.toString() } ?: udphop.getString("ports")?.takeIf { it.isNotEmpty() }?.also { // invalid port is ignored hysteria2Bean.serverPorts = (it.split(",").joinToString(",") { it.trim() }) .takeIf { it.isValidHysteriaPort(disallowFromGreaterThanTo = true) } } udphop.getLong("interval")?.also { hysteria2Bean.hopInterval = it.takeIf { it > 0 } } ?: udphop.getString("interval")?.also { val intervalLong = it.toLongOrNull() if (intervalLong != null && intervalLong > 0) { hysteria2Bean.hopInterval = intervalLong } else { val intervalStringList = it.split("-") if (intervalStringList.size == 2) { val intervalLong0 = intervalStringList[0].toLongOrNull() val intervalLong1 = intervalStringList[1].toLongOrNull() if (intervalLong0 != null && intervalLong0 > 0 && intervalLong1 != null && intervalLong1 > 0) { hysteria2Bean.hopIntervalMin = minOf(intervalLong0, intervalLong1) hysteria2Bean.hopIntervalMax = maxOf(intervalLong0, intervalLong1) } } } } } } } streamSettings.getString("security")?.lowercase()?.also { security -> when (security) { "tls" -> { streamSettings.getObject("tlsSettings")?.also { tlsSettings -> tlsSettings.getString("serverName")?.also { hysteria2Bean.sni = it } tlsSettings.getBoolean("allowInsecure")?.also { hysteria2Bean.allowInsecure = it } tlsSettings.getArray("certificates")?.asReversed()?.forEach { certificate -> when (certificate.getString("usage")?.lowercase()) { null, "", "encipherment" -> { if (!certificate.contains("certificateFile") && !certificate.contains("keyFile")) { val cert = certificate.getStringArray("certificate")?.joinToString("\n")?.takeIf { it.contains("-----BEGIN ") && it.contains("-----END ") && it.contains(" CERTIFICATE-----") } val key = certificate.getStringArray("key")?.joinToString("\n")?.takeIf { it.contains("-----BEGIN ") && it.contains("-----END ") && it.contains(" PRIVATE KEY-----") } if (cert != null && key != null) { hysteria2Bean.mtlsCertificate = cert hysteria2Bean.mtlsCertificatePrivateKey = key } } } "verify" -> { if (!certificate.contains("certificateFile")) { val cert = certificate.getStringArray("certificate")?.joinToString("\n")?.takeIf { it.contains("-----BEGIN ") && it.contains("-----END ") && it.contains(" CERTIFICATE-----") } if (cert != null) { hysteria2Bean.certificates = cert } } } } } tlsSettings.getString("pinnedPeerCertSha256")?.takeIf { it.isNotEmpty() }?.also { pcs -> hysteria2Bean.pinnedPeerCertificateSha256 = pcs.split(",") .mapNotNull { it.trim().ifEmpty { null }?.replace(":", "") } .joinToString("\n") if (!hysteria2Bean.pinnedPeerCertificateSha256.isNullOrEmpty()) { hysteria2Bean.allowInsecure = true } } tlsSettings.getString("verifyPeerCertByName")?.split(",") ?.filter { it.isNotEmpty() }?.takeIf { it.isNotEmpty() }?.also { hysteria2Bean.serverNameToVerify = it.joinToString("\n") } tlsSettings.getString("echConfigList")?.also { hysteria2Bean.echEnabled = true try { Base64.getDecoder().decode(it) hysteria2Bean.echConfig = it } catch (_: Exception) {} } } } else -> return listOf() } } } return listOf(hysteria2Bean) } "trusttunnel" -> { val trusttunnelBean = TrustTunnelBean() outbound.getObject("settings")?.also { settings -> outbound.getString("tag")?.also { trusttunnelBean.name = it } settings.getString("address")?.also { trusttunnelBean.serverAddress = it } ?: return listOf() settings.getPort("port")?.also { trusttunnelBean.serverPort = it } ?: return listOf() settings.getString("username")?.also { trusttunnelBean.username = it } settings.getString("password")?.also { trusttunnelBean.password = it } settings.getString("serverNameToVerify")?.also { // for old Owenclave backward compatibility trusttunnelBean.serverNameToVerify = it } settings.getBoolean("http3")?.also { trusttunnelBean.protocol = if (it) "quic" else "https" } } outbound.getObject("streamSettings")?.also { streamSettings -> var tlsSettingsObject: JsonObject? = null when (streamSettings.getString("security")) { "tls" -> tlsSettingsObject = streamSettings.getObject("tlsSettings") "utls" -> streamSettings.getObject("utlsSettings")?.also { tlsSettingsObject = it.getObject("tlsConfig") } else -> return listOf() } tlsSettingsObject?.also { tlsSettings -> tlsSettings.getString("serverName")?.also { trusttunnelBean.sni = it } tlsSettings.getBoolean("allowInsecure")?.also { trusttunnelBean.allowInsecure = it } tlsSettings.getArray("certificates")?.asReversed()?.forEach { certificate -> when (certificate.getString("usage")?.lowercase()) { null, "", "encipherment" -> { if (!certificate.contains("certificateFile") && !certificate.contains("keyFile")) { val cert = certificate.getStringArray("certificate")?.joinToString("\n")?.takeIf { it.contains("-----BEGIN ") && it.contains("-----END ") && it.contains(" CERTIFICATE-----") } val key = certificate.getStringArray("key")?.joinToString("\n")?.takeIf { it.contains("-----BEGIN ") && it.contains("-----END ") && it.contains(" PRIVATE KEY-----") } if (cert != null && key != null) { trusttunnelBean.mtlsCertificate = cert trusttunnelBean.mtlsCertificatePrivateKey = key } } } "verify" -> { if (!certificate.contains("certificateFile")) { val cert = certificate.getStringArray("certificate")?.joinToString("\n")?.takeIf { it.contains("-----BEGIN ") && it.contains("-----END ") && it.contains(" CERTIFICATE-----") } if (cert != null) { trusttunnelBean.certificate = cert } } } } } tlsSettings.getStringArray("pinnedPeerCertificateChainSha256")?.also { trusttunnelBean.pinnedPeerCertificateChainSha256 = it.joinToString("\n") tlsSettings.getBoolean("allowInsecureIfPinnedPeerCertificate")?.also { allowInsecure -> trusttunnelBean.allowInsecure = allowInsecure } } tlsSettings.getStringArray("pinnedPeerCertificatePublicKeySha256")?.also { trusttunnelBean.pinnedPeerCertificatePublicKeySha256 = it.joinToString("\n") tlsSettings.getBoolean("allowInsecureIfPinnedPeerCertificate")?.also { allowInsecure -> trusttunnelBean.allowInsecure = allowInsecure } } tlsSettings.getStringArray("pinnedPeerCertificateSha256")?.also { trusttunnelBean.pinnedPeerCertificateSha256 = it.joinToString("\n") tlsSettings.getBoolean("allowInsecureIfPinnedPeerCertificate")?.also { allowInsecure -> trusttunnelBean.allowInsecure = allowInsecure } } tlsSettings.getStringArray("serverNameToVerify")?.also { trusttunnelBean.serverNameToVerify = it.joinToString("\n") } /*tlsSettings.getObject("ech")?.also { trusttunnelBean.echEnabled = it.getBoolean("enabled") trusttunnelBean.echConfig = it.getString("config") }*/ } } return listOf(trusttunnelBean) } else -> return listOf() } } private fun JsonObject.contains(key: String, ignoreCase: Boolean = true): Boolean { val value = get(key) when { value == null -> if (!ignoreCase) return false value.isJsonNull -> if (!ignoreCase) return false else -> return true } for ((k, v) in entrySet()) { if (k.equals(key, ignoreCase = true) && !v.isJsonNull) { return true } } return false } private fun JsonObject.getString(key: String, ignoreCase: Boolean = true): String? { val value = get(key) when { value == null -> if (!ignoreCase) return null value.isJsonNull -> if (!ignoreCase) return null value.isJsonPrimitive -> return if (value.asJsonPrimitive.isString) value.asString else null else -> return null } for ((k, v) in entrySet()) { if (k.equals(key, ignoreCase = true) && v.isJsonPrimitive && v.asJsonPrimitive.isString) { return v.asString } } return null } private fun JsonObject.getInt(key: String, ignoreCase: Boolean = true): Int? { val value = get(key) when { value == null -> if (!ignoreCase) return null value.isJsonNull -> if (!ignoreCase) return null value.isJsonPrimitive -> { return if (value.asJsonPrimitive.isNumber) { try { value.asInt } catch (_: Exception) { null } } else { null } } } for ((k, v) in entrySet()) { if (k.equals(key, ignoreCase = true) && v.isJsonPrimitive && v.asJsonPrimitive.isNumber) { try { return v.asInt } catch (_: Exception) {} } } return null } private fun JsonObject.getBoolean(key: String, ignoreCase: Boolean = true): Boolean? { val value = get(key) when { value == null -> if (!ignoreCase) return null value.isJsonNull -> if (!ignoreCase) return null value.isJsonPrimitive -> return if (value.asJsonPrimitive.isBoolean) value.asBoolean else null else -> return null } for ((k, v) in entrySet()) { if (k.equals(key, ignoreCase = true) && v.isJsonPrimitive && v.asJsonPrimitive.isBoolean) { return v.asBoolean } } return null } private fun JsonObject.getLong(key: String, ignoreCase: Boolean = true): Long? { val value = get(key) when { value == null -> if (!ignoreCase) return null value.isJsonNull -> if (!ignoreCase) return null value.isJsonPrimitive -> { return if (value.asJsonPrimitive.isNumber) { try { value.asLong } catch (_: Exception) { null } } else { null } } } for ((k, v) in entrySet()) { if (k.equals(key, ignoreCase = true) && v.isJsonPrimitive && v.asJsonPrimitive.isNumber) { try { return v.asLong } catch (_: Exception) {} } } return null } private fun JsonObject.getObject(key: String, ignoreCase: Boolean = true): JsonObject? { val value = get(key) when { value == null -> if (!ignoreCase) return null value.isJsonNull -> if (!ignoreCase) return null value.isJsonObject -> return value.asJsonObject else -> return null } for ((k, v) in entrySet()) { if (k.equals(key, ignoreCase = true) && v.isJsonObject) { return v.asJsonObject } } return null } private fun JsonObject.getJsonArray(key: String, ignoreCase: Boolean = true): JsonArray? { val value = get(key) when { value == null -> if (!ignoreCase) return null value.isJsonNull -> if (!ignoreCase) return null value.isJsonArray -> return value.asJsonArray else -> return null } for ((k, v) in entrySet()) { if (k.equals(key, ignoreCase = true) && v.isJsonArray) { return v.asJsonArray } } return null } private fun JsonObject.getArray(key: String, ignoreCase: Boolean = true): List? { val jsonArray = getJsonArray(key, ignoreCase) ?: return null return try { Gson().fromJson(jsonArray, Array::class.java)?.asList() } catch (_: Exception) { null } } private fun JsonObject.getStringArray(key: String, ignoreCase: Boolean = true): List? { val jsonArray = getJsonArray(key, ignoreCase) ?: return null return try { Gson().fromJson(jsonArray, Array::class.java)?.asList() } catch (_: Exception) { null } } private fun JsonObject.getIntArray(key: String, ignoreCase: Boolean = true): List? { val jsonArray = getJsonArray(key, ignoreCase) ?: return null return try { Gson().fromJson(jsonArray, Array::class.java)?.asList() } catch (_: Exception) { null } } private fun JsonObject.getPort(key: String): Int? { val value = get(key) when { value == null -> {} value.isJsonNull -> {} value.isJsonPrimitive && value.asJsonPrimitive.isNumber -> { return try { value.asInt } catch (_: Exception) { null } } value.isJsonPrimitive && value.asJsonPrimitive.isString -> return value.asString.toIntOrNull() else -> return null } for ((k, v) in entrySet()) { if (k.equals(key, ignoreCase = true) && v.isJsonPrimitive) { when { v.asJsonPrimitive.isNumber -> { try { return v.asInt } catch (_: Exception) {} } v.asJsonPrimitive.isString -> return v.asString.toIntOrNull() } } } return null }