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

github - dev - back to tree - upstream
/****************************************************************************** * * * Copyright (C) 2024 dyhkwong * * * * 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.google.gson.Gson import com.google.gson.JsonObject import io.nekohasekai.sagernet.fmt.AbstractBean import io.nekohasekai.sagernet.fmt.http.HttpBean import io.nekohasekai.sagernet.fmt.hysteria2.Hysteria2Bean 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.socks.SOCKSBean import io.nekohasekai.sagernet.fmt.trojan.TrojanBean import io.nekohasekai.sagernet.fmt.v2ray.VLESSBean import io.nekohasekai.sagernet.fmt.v2ray.VMessBean import io.nekohasekai.sagernet.fmt.v2ray.supportedQuicSecurity import io.nekohasekai.sagernet.fmt.wireguard.WireGuardBean import io.nekohasekai.sagernet.ktx.* import kotlin.io.encoding.Base64 fun parseV2Ray5Outbound(outbound: JsonObject): List { when (val type = outbound.getString("protocol")) { "shadowsocks", "trojan", "vmess", "vless", "socks", "http", "shadowsocks2022" -> { val v2rayBean = when (type) { "shadowsocks", "shadowsocks2022" -> ShadowsocksBean() "trojan" -> TrojanBean() "vmess" -> VMessBean() "vless" -> VLESSBean() "socks" -> SOCKSBean() "http" -> HttpBean() else -> return listOf() }.apply { outbound.getString("tag")?.also { name = it } } outbound.getObject("streamSettings")?.also { streamSettings -> if (streamSettings.contains("network", ignoreCase = true) || streamSettings.contains("tlsSettings", ignoreCase = true) || streamSettings.contains("xtlsSettings", ignoreCase = true) || streamSettings.contains("utlsSettings", ignoreCase = true) || streamSettings.contains("tcpSettings", ignoreCase = true) || streamSettings.contains("kcpSettings", ignoreCase = true) || streamSettings.contains("wsSettings", ignoreCase = true) || streamSettings.contains("httpSettings", ignoreCase = true) || streamSettings.contains("grpcSettings", ignoreCase = true) || streamSettings.contains("gunSettings", ignoreCase = true) || streamSettings.contains("quicSettings", ignoreCase = true) || streamSettings.contains("hy2Settings", ignoreCase = true) || streamSettings.contains("rawSettings", ignoreCase = true) || streamSettings.contains("splithttpSettings", ignoreCase = true) || streamSettings.contains("xhttpSettings", ignoreCase = true) ) { // jsonv4 return listOf() } streamSettings.getString("security")?.also { security -> when (security) { "none", "" -> {} "tls", "utls" -> { v2rayBean.security = "tls" val securitySettings = streamSettings.getObject("securitySettings") val tls = if (security == "tls") { securitySettings } else { securitySettings?.getObject("tlsConfig") ?: securitySettings?.getObject("tls_config") } tls?.also { tlsConfig -> (tlsConfig.getString("serverName") ?: tlsConfig.getString("server_name"))?.also { v2rayBean.sni = it } tlsConfig.getStringArray("nextProtocol")?.also { v2rayBean.alpn = it.joinToString("\n") } ?: tlsConfig.getStringArray("next_protocol")?.also { v2rayBean.alpn = it.joinToString("\n") } tlsConfig.getArray("certificate")?.asReversed()?.forEach { certificate -> when (certificate.getString("usage")) { null, "ENCIPHERMENT" -> { if (!certificate.has("certificateFile") && !certificate.has("certificate_file") && !certificate.has("keyFile") && !certificate.has("key_file")) { val cert = certificate.getByteArray("Certificate") val key = certificate.getByteArray("Key") if (cert != null && key != null) { try { v2rayBean.mtlsCertificate = String(cert).takeIf { it.contains("-----BEGIN ") && it.contains("-----END ") && it.contains(" CERTIFICATE-----") } v2rayBean.mtlsCertificatePrivateKey = String(key).takeIf { it.contains("-----BEGIN ") && it.contains("-----END ") && it.contains(" PRIVATE KEY-----") } } catch (_: Exception) {} } } } "AUTHORITY_VERIFY" -> { if (!certificate.has("certificateFile") && !certificate.has("certificate_file")) { val cert = certificate.getByteArray("Certificate") if (cert != null) { try { v2rayBean.certificates = String(cert).takeIf { it.contains("-----BEGIN ") && it.contains("-----END ") && it.contains(" CERTIFICATE-----") } } catch (_: Exception) {} } } } } } (tlsConfig.getByteArrayArray("pinnedPeerCertificateChainSha256") ?: tlsConfig.getByteArrayArray("pinned_peer_certificate_chain_sha256"))?.also { v2rayBean.pinnedPeerCertificateChainSha256 = it.joinToString("\n") { Base64.encode(it) } (tlsConfig.getBoolean("allowInsecureIfPinnedPeerCertificate") ?: tlsConfig.getBoolean("allow_insecure_if_pinned_peer_certificate"))?.also { allowInsecure -> v2rayBean.allowInsecure = allowInsecure } } if (v2rayBean is VLESSBean || v2rayBean is TrojanBean || v2rayBean is VMessBean) { tlsConfig.getString("echDohServer")?.also { v2rayBean.echEnabled = true } tlsConfig.getString("echConfig")?.also { v2rayBean.echEnabled = true v2rayBean.echConfig = it } } } } else -> return listOf() } } streamSettings.getString("transport")?.also { transport -> when (transport) { "tcp", "" -> { v2rayBean.type = "tcp" streamSettings.getObject("transportSettings")?.also { transportSettings -> (transportSettings.getObject("headerSettings") ?: transportSettings.getObject("header_settings"))?.also { headerSettings -> when (headerSettings.getString("@type")) { "v2ray.core.transport.internet.headers.http.Config" -> { v2rayBean.headerType = "http" headerSettings.getObject("request")?.also { request -> request.getStringArray("uri")?.also { v2rayBean.path = it.joinToString("\n") } request.getArray("header")?.forEach { if (it.getString("name")?.lowercase() == "host") { v2rayBean.host = it.getStringArray("value")?.joinToString("\n") } } } } } } } } "kcp" -> { v2rayBean.type = "kcp" streamSettings.getObject("transportSettings")?.also { transportSettings -> transportSettings.getString("seed")?.also { v2rayBean.mKcpSeed = it } (transportSettings.getObject("headerConfig") ?: transportSettings.getObject("header_config"))?.also { headerConfig -> when (headerConfig.getString("@type")) { null, "types.v2fly.org/v2ray.core.transport.internet.headers.noop.Config", "types.v2fly.org/v2ray.core.transport.internet.headers.noop.ConnectionConfig" -> v2rayBean.headerType = "none" "types.v2fly.org/v2ray.core.transport.internet.headers.srtp.Config" -> v2rayBean.headerType = "srtp" "types.v2fly.org/v2ray.core.transport.internet.headers.utp.Config" -> v2rayBean.headerType = "utp" "types.v2fly.org/v2ray.core.transport.internet.headers.wechat.VideoConfig" -> v2rayBean.headerType = "wechat-video" "types.v2fly.org/v2ray.core.transport.internet.headers.tls.PacketConfig" -> v2rayBean.headerType = "dtls" "types.v2fly.org/v2ray.core.transport.internet.headers.wireguard.WireguardConfig" -> v2rayBean.headerType = "wireguard" else -> return listOf() } } } } "ws" -> { v2rayBean.type = "ws" streamSettings.getObject("transportSettings")?.also { transportSettings -> transportSettings.getString("path")?.also { v2rayBean.path = it } (transportSettings.getInt("maxEarlyData") ?: transportSettings.getInt("max_early_data"))?.also { v2rayBean.maxEarlyData = it } (transportSettings.getString("earlyDataHeaderName") ?: transportSettings.getString("early_data_header_name"))?.also { v2rayBean.earlyDataHeaderName = it } transportSettings.getArray("header")?.forEach { if (it.getString("key")?.lowercase() == "host") { v2rayBean.host = it.getStringArray("value")?.joinToString("\n") } } } } "h2" -> { v2rayBean.type = "http" streamSettings.getObject("transportSettings")?.also { transportSettings -> transportSettings.getString("path")?.also { v2rayBean.path = it } transportSettings.getStringArray("host")?.also { v2rayBean.host = it.joinToString("\n") } } } "quic" -> { v2rayBean.type = "quic" streamSettings.getObject("transportSettings")?.also { transportSettings -> transportSettings.getString("security")?.lowercase()?.also { if (it !in supportedQuicSecurity) return listOf() v2rayBean.quicSecurity = it } transportSettings.getString("key")?.also { v2rayBean.quicKey = it } (transportSettings.getObject("headerConfig") ?: transportSettings.getObject("header_config"))?.also { headerConfig -> when (headerConfig.getString("@type")) { null, "types.v2fly.org/v2ray.core.transport.internet.headers.noop.Config", "types.v2fly.org/v2ray.core.transport.internet.headers.noop.ConnectionConfig" -> v2rayBean.headerType = "none" "types.v2fly.org/v2ray.core.transport.internet.headers.srtp.Config" -> v2rayBean.headerType = "srtp" "types.v2fly.org/v2ray.core.transport.internet.headers.utp.Config" -> v2rayBean.headerType = "utp" "types.v2fly.org/v2ray.core.transport.internet.headers.wechat.VideoConfig" -> v2rayBean.headerType = "wechat-video" "types.v2fly.org/v2ray.core.transport.internet.headers.tls.PacketConfig" -> v2rayBean.headerType = "dtls" "types.v2fly.org/v2ray.core.transport.internet.headers.wireguard.WireguardConfig" -> v2rayBean.headerType = "wireguard" else -> return listOf() } } } } "grpc" -> { v2rayBean.type = "grpc" streamSettings.getObject("transportSettings")?.also { transportSettings -> (transportSettings.getString("serviceName") ?: transportSettings.getString("service_name"))?.also { v2rayBean.grpcServiceName = it } } } "httpupgrade" -> { v2rayBean.type = "httpupgrade" streamSettings.getObject("transportSettings")?.also { transportSettings -> transportSettings.getString("path")?.also { v2rayBean.path = it } transportSettings.getString("host")?.also { v2rayBean.host = it } (transportSettings.getInt("maxEarlyData") ?: transportSettings.getInt("max_early_data"))?.also { v2rayBean.maxEarlyData = it } (transportSettings.getString("earlyDataHeaderName") ?: transportSettings.getString("early_data_header_name"))?.also { v2rayBean.earlyDataHeaderName = it } } } "meek" -> { v2rayBean.type = "meek" streamSettings.getObject("transportSettings")?.also { transportSettings -> transportSettings.getString("url")?.also { v2rayBean.meekUrl = it } } } "mekya" -> { v2rayBean.type = "mekya" streamSettings.getObject("transportSettings")?.also { transportSettings -> transportSettings.getString("url")?.also { v2rayBean.mekyaUrl = it } transportSettings.getObject("kcp")?.also { kcp -> kcp.getString("seed")?.also { v2rayBean.mekyaKcpSeed = it } (kcp.getObject("headerConfig") ?: kcp.getObject("header_config"))?.also { headerConfig -> when (headerConfig.getString("@type")) { null, "types.v2fly.org/v2ray.core.transport.internet.headers.noop.Config", "types.v2fly.org/v2ray.core.transport.internet.headers.noop.ConnectionConfig" -> v2rayBean.mekyaKcpHeaderType = "none" "types.v2fly.org/v2ray.core.transport.internet.headers.srtp.Config" -> v2rayBean.mekyaKcpHeaderType = "srtp" "types.v2fly.org/v2ray.core.transport.internet.headers.utp.Config" -> v2rayBean.mekyaKcpHeaderType = "utp" "types.v2fly.org/v2ray.core.transport.internet.headers.wechat.VideoConfig" -> v2rayBean.mekyaKcpHeaderType = "wechat-video" "types.v2fly.org/v2ray.core.transport.internet.headers.tls.PacketConfig" -> v2rayBean.mekyaKcpHeaderType = "dtls" "types.v2fly.org/v2ray.core.transport.internet.headers.wireguard.WireguardConfig" -> v2rayBean.mekyaKcpHeaderType = "wireguard" else -> return listOf() } } } } } "hysteria2" -> { v2rayBean.type = "hysteria2" streamSettings.getObject("transportSettings")?.also { transportSettings -> transportSettings.getString("password")?.also { v2rayBean.hy2Password = it } } } else -> return listOf() } } } outbound.getObject("settings")?.also { settings -> if (settings.contains("servers", ignoreCase = true) || settings.contains("vnext", ignoreCase = true)) { // jsonv4 return listOf() } settings.getString("address")?.also { v2rayBean.serverAddress = it } ?: return listOf() settings.getInt("port")?.also { v2rayBean.serverPort = it } ?: return listOf() when (type) { "shadowsocks" -> { v2rayBean as ShadowsocksBean 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 } } "trojan" -> { v2rayBean as TrojanBean settings.getString("password")?.also { v2rayBean.password = it } } "vmess" -> { v2rayBean as VMessBean settings.getString("uuid")?.also { v2rayBean.uuid = it } } "vless" -> { v2rayBean as VLESSBean settings.getString("uuid")?.also { v2rayBean.uuid = it } } "shadowsocks2022" -> { v2rayBean as ShadowsocksBean settings.getString("method")?.also { if (it !in supportedShadowsocks2022Method) return listOf() v2rayBean.method = it } settings.getByteArray("psk")?.also { psk -> v2rayBean.password = Base64.encode(psk) settings.getByteArrayArray("ipsk")?.also { ipsk -> v2rayBean.password = ipsk.joinToString(":") { Base64.encode(it) } + ":" + Base64.encode(psk) } } } } } return listOf(v2rayBean) } "hysteria2" -> { val hysteria2Bean = Hysteria2Bean().apply { outbound.getString("tag")?.also { name = it } } outbound.getObject("streamSettings")?.also { streamSettings -> if (streamSettings.getString("security") != "tls") { return listOf() } if (streamSettings.getString("transport") != "hysteria2") { return listOf() } streamSettings.getObject("securitySettings")?. also { securitySettings -> (securitySettings.getString("serverName") ?: securitySettings.getString("server_name"))?.also { hysteria2Bean.sni = it } securitySettings.getString("echDohServer")?.also { hysteria2Bean.echEnabled = true } securitySettings.getString("echConfig")?.also { hysteria2Bean.echEnabled = true hysteria2Bean.echConfig = it } } streamSettings.getObject("transportSettings")?.also { transportSettings -> transportSettings.getString("password")?.also { hysteria2Bean.auth = it } } } outbound.getObject("settings")?.also { settings -> settings.getArray("server")?.forEach { server -> server.getString("address")?.also { hysteria2Bean.serverAddress = it } ?: return listOf() server.getInt("port")?.also { hysteria2Bean.serverPorts = it.toString() } ?: return listOf() } } ?: return listOf() return listOf(hysteria2Bean) } "wireguard" -> { val beanList = mutableListOf() val wireguardBean = WireGuardBean().apply { outbound.getString("tag")?.also { name = it } } outbound.getObject("settings")?.also { settings -> settings.getObject("stack")?.also { stack -> val ips = mutableListOf() stack.getArray("ips")?.forEach { ip -> val ipAddr = ip.getString("ip_addr") ?: ip.getString("ipAddr") val prefix = ip.getInt("prefix") if (!ipAddr.isNullOrEmpty() && prefix != null) { ips.add("$ipAddr/$prefix") } } if (ips.isEmpty()) return listOf() wireguardBean.localAddress = ips.joinToString("\n") wireguardBean.mtu = stack.getInt("mtu") } (settings.getObject("wg_device") ?: outbound.getObject("wgDevice"))?.also { wgDevice -> wireguardBean.privateKey = (wgDevice.getString("private_key") ?: wgDevice.getString("privateKey")) ?: return listOf() wgDevice.getArray("peers")?.forEach { peer -> beanList.add(wireguardBean.applyDefaultValues().clone().apply { peerPublicKey = peer.getString("public_key") ?: peer.getString("publicKey") peerPreSharedKey = peer.getString("preshared_key") ?: peer.getString("presharedKey") keepaliveInterval = peer.getInt("persistent_keepalive_interval") ?: peer.getInt("persistentKeepaliveInterval") peer.getString("endpoint")?.also { serverAddress = it.substringBeforeLast(":").removePrefix("[").removeSuffix("]") serverPort = it.substringAfterLast(":").toIntOrNull() ?: return listOf() } }) } } } ?: return listOf() return beanList } else -> return listOf() } } private fun JsonObject.getInt(key: String): Int? { val value = get(key) ?: return null return when { value.isJsonPrimitive && value.asJsonPrimitive.isNumber -> { try { value.asInt } catch (_: Exception) { null } } value.isJsonPrimitive && value.asJsonPrimitive.isString -> value.asString.toIntOrNull() else -> null } } private fun JsonObject.getByteArray(key: String): ByteArray? { val value = get(key) ?: return null return when { value.isJsonPrimitive && value.asJsonPrimitive.isString -> { try { Base64.decode(value.asString.toByteArray()) } catch (_: Exception) { null } } value.isJsonArray -> { try { Gson().fromJson(value, ByteArray::class.java) } catch (_: Exception) { null } } else -> null } } private fun JsonObject.getByteArrayArray(key: String): Array? { val jsonArray = getJsonArray(key) ?: return null val ret = mutableListOf() for (value in jsonArray) { when { value.isJsonPrimitive && value.asJsonPrimitive.isString -> { ret.add( try { Base64.decode(value.asString.toByteArray()) } catch (_: Exception) { return null } ) } value.isJsonArray -> { ret.add( try { Gson().fromJson(value, ByteArray::class.java) } catch (_: Exception) { return null } ) } value.isJsonNull -> continue else -> return null } } return ret.toTypedArray() }