Commit 6270308e authored by Mygod's avatar Mygod

Prevent OOM when reading invalid file

parent f6ec072e
...@@ -58,6 +58,7 @@ dependencies { ...@@ -58,6 +58,7 @@ dependencies {
api "androidx.room:room-runtime:$roomVersion" api "androidx.room:room-runtime:$roomVersion"
api 'androidx.work:work-runtime-ktx:2.1.0-beta01' api 'androidx.work:work-runtime-ktx:2.1.0-beta01'
api 'com.crashlytics.sdk.android:crashlytics:2.10.1' api 'com.crashlytics.sdk.android:crashlytics:2.10.1'
api 'com.google.code.gson:gson:2.8.5'
api 'com.google.firebase:firebase-config:17.0.0' api 'com.google.firebase:firebase-config:17.0.0'
api 'com.google.firebase:firebase-core:16.0.9' api 'com.google.firebase:firebase-core:16.0.9'
api 'dnsjava:dnsjava:2.1.9' api 'dnsjava:dnsjava:2.1.9'
......
...@@ -32,12 +32,14 @@ import com.github.shadowsocks.plugin.PluginConfiguration ...@@ -32,12 +32,14 @@ import com.github.shadowsocks.plugin.PluginConfiguration
import com.github.shadowsocks.plugin.PluginOptions import com.github.shadowsocks.plugin.PluginOptions
import com.github.shadowsocks.preference.DataStore import com.github.shadowsocks.preference.DataStore
import com.github.shadowsocks.utils.Key import com.github.shadowsocks.utils.Key
import com.github.shadowsocks.utils.asIterable
import com.github.shadowsocks.utils.parsePort import com.github.shadowsocks.utils.parsePort
import com.google.gson.JsonArray
import com.google.gson.JsonElement
import com.google.gson.JsonObject
import com.google.gson.JsonPrimitive
import kotlinx.android.parcel.Parcelize import kotlinx.android.parcel.Parcelize
import org.json.JSONArray import org.json.JSONArray
import org.json.JSONObject import org.json.JSONObject
import org.json.JSONTokener
import java.io.Serializable import java.io.Serializable
import java.net.URI import java.net.URI
import java.net.URISyntaxException import java.net.URISyntaxException
...@@ -134,14 +136,23 @@ data class Profile( ...@@ -134,14 +136,23 @@ data class Profile(
private class JsonParser(private val feature: Profile? = null) : ArrayList<Profile>() { private class JsonParser(private val feature: Profile? = null) : ArrayList<Profile>() {
private val fallbackMap = mutableMapOf<Profile, Profile>() private val fallbackMap = mutableMapOf<Profile, Profile>()
private fun tryParse(json: JSONObject, fallback: Boolean = false): Profile? { private val JsonElement?.optString get() = (this as? JsonPrimitive)?.asString
val host = json.optString("server") private val JsonElement?.optBoolean get() = // asBoolean attempts to cast everything to boolean
(this as? JsonPrimitive)?.run { if (isBoolean) asBoolean else null }
private val JsonElement?.optInt get() = try {
(this as? JsonPrimitive)?.asInt
} catch (_: NumberFormatException) {
null
}
private fun tryParse(json: JsonObject, fallback: Boolean = false): Profile? {
val host = json["server"].optString
if (host.isNullOrEmpty()) return null if (host.isNullOrEmpty()) return null
val remotePort = json.optInt("server_port") val remotePort = json["server_port"]?.optInt
if (remotePort <= 0) return null if (remotePort == null || remotePort <= 0) return null
val password = json.optString("password") val password = json["password"].optString
if (password.isNullOrEmpty()) return null if (password.isNullOrEmpty()) return null
val method = json.optString("method") val method = json["method"].optString
if (method.isNullOrEmpty()) return null if (method.isNullOrEmpty()) return null
return Profile().also { return Profile().also {
it.host = host it.host = host
...@@ -150,33 +161,34 @@ data class Profile( ...@@ -150,33 +161,34 @@ data class Profile(
it.method = method it.method = method
}.apply { }.apply {
feature?.copyFeatureSettingsTo(this) feature?.copyFeatureSettingsTo(this)
val id = json.optString("plugin") val id = json["plugin"].optString
if (!id.isNullOrEmpty()) { if (!id.isNullOrEmpty()) {
plugin = PluginOptions(id, json.optString("plugin_opts")).toString(false) plugin = PluginOptions(id, json["plugin_opts"].optString).toString(false)
} }
name = json.optString("remarks") name = json["remarks"].optString
route = json.optString("route", route) route = json["route"].optString ?: route
if (fallback) return@apply if (fallback) return@apply
remoteDns = json.optString("remote_dns", remoteDns) remoteDns = json["remote_dns"].optString ?: remoteDns
ipv6 = json.optBoolean("ipv6", ipv6) ipv6 = json["ipv6"].optBoolean ?: ipv6
metered = json.optBoolean("metered", metered) metered = json["metered"].optBoolean ?: metered
json.optJSONObject("proxy_apps")?.also { (json["proxy_apps"] as? JsonObject)?.also {
proxyApps = it.optBoolean("enabled", proxyApps) proxyApps = it["enabled"].optBoolean ?: proxyApps
bypass = it.optBoolean("bypass", bypass) bypass = it["bypass"].optBoolean ?: bypass
individual = it.optJSONArray("android_list")?.asIterable()?.joinToString("\n") ?: individual individual = (json["android_list"] as? JsonArray)?.asIterable()?.joinToString("\n") ?:
individual
} }
udpdns = json.optBoolean("udpdns", udpdns) udpdns = json["udpdns"].optBoolean ?: udpdns
json.optJSONObject("udp_fallback")?.let { tryParse(it, true) }?.also { fallbackMap[this] = it } (json["udp_fallback"] as? JsonObject)?.let { tryParse(it, true) }?.also { fallbackMap[this] = it }
} }
} }
fun process(json: Any) { fun process(json: JsonElement?) {
when (json) { when (json) {
is JSONObject -> { is JsonObject -> {
val profile = tryParse(json) val profile = tryParse(json)
if (profile != null) add(profile) else for (key in json.keys()) process(json.get(key)) if (profile != null) add(profile) else for ((_, value) in json.entrySet()) process(value)
} }
is JSONArray -> json.asIterable().forEach(this::process) is JsonArray -> json.asIterable().forEach(this::process)
// ignore other types // ignore other types
} }
} }
...@@ -196,8 +208,8 @@ data class Profile( ...@@ -196,8 +208,8 @@ data class Profile(
} }
} }
} }
fun parseJson(json: String, feature: Profile? = null, create: (Profile) -> Unit) = JsonParser(feature).run { fun parseJson(json: JsonElement, feature: Profile? = null, create: (Profile) -> Unit) = JsonParser(feature).run {
process(JSONTokener(json).nextValue()) process(json)
for (profile in this) create(profile) for (profile in this) create(profile)
finalize(create) finalize(create)
} }
......
...@@ -26,6 +26,8 @@ import com.github.shadowsocks.Core ...@@ -26,6 +26,8 @@ import com.github.shadowsocks.Core
import com.github.shadowsocks.preference.DataStore import com.github.shadowsocks.preference.DataStore
import com.github.shadowsocks.utils.DirectBoot import com.github.shadowsocks.utils.DirectBoot
import com.github.shadowsocks.utils.printLog import com.github.shadowsocks.utils.printLog
import com.google.gson.JsonParser
import com.google.gson.stream.JsonReader
import org.json.JSONArray import org.json.JSONArray
import java.io.IOException import java.io.IOException
import java.io.InputStream import java.io.InputStream
...@@ -52,6 +54,7 @@ object ProfileManager { ...@@ -52,6 +54,7 @@ object ProfileManager {
return profile return profile
} }
private val jsonParser = JsonParser()
fun createProfilesFromJson(jsons: Sequence<InputStream>, replace: Boolean = false) { fun createProfilesFromJson(jsons: Sequence<InputStream>, replace: Boolean = false) {
val profiles = if (replace) getAllProfiles()?.associateBy { it.formattedAddress } else null val profiles = if (replace) getAllProfiles()?.associateBy { it.formattedAddress } else null
val feature = if (replace) { val feature = if (replace) {
...@@ -60,7 +63,9 @@ object ProfileManager { ...@@ -60,7 +63,9 @@ object ProfileManager {
val lazyClear = lazy { clear() } val lazyClear = lazy { clear() }
var result: Exception? = null var result: Exception? = null
for (json in jsons) try { for (json in jsons) try {
Profile.parseJson(json.bufferedReader().readText(), feature) { Profile.parseJson(jsonParser.parse(JsonReader(json.bufferedReader()).apply {
isLenient = true
}), feature) {
if (replace) { if (replace) {
lazyClear.value lazyClear.value
// if two profiles has the same address, treat them as the same profile and copy stats over // if two profiles has the same address, treat them as the same profile and copy stats over
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment