diff --git a/app/src/main/java/com/futo/platformplayer/activities/AddSourceActivity.kt b/app/src/main/java/com/futo/platformplayer/activities/AddSourceActivity.kt index e63a9866..fd12528e 100644 --- a/app/src/main/java/com/futo/platformplayer/activities/AddSourceActivity.kt +++ b/app/src/main/java/com/futo/platformplayer/activities/AddSourceActivity.kt @@ -42,7 +42,8 @@ class AddSourceActivity : AppCompatActivity() { private val _client = ManagedHttpClient(); - private var _config : SourcePluginConfig? = null; + private var _config: SourcePluginConfig? = null; + private var _script: String? = null; override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState); @@ -81,7 +82,7 @@ class AddSourceActivity : AppCompatActivity() { } _buttonInstall.setOnClickListener { _config?.let { - install(_config!!); + install(_config!!, _script!!); }; }; @@ -114,6 +115,7 @@ class AddSourceActivity : AppCompatActivity() { setLoading(true); lifecycleScope.launch(Dispatchers.IO) { + val config: SourcePluginConfig; try { val configResp = _client.get(url); if(!configResp.isOk) @@ -121,33 +123,51 @@ class AddSourceActivity : AppCompatActivity() { val configJson = configResp.body?.string(); if(configJson.isNullOrEmpty()) throw IllegalStateException("No response"); - val config = SourcePluginConfig.fromJson(configJson, url); - withContext(Dispatchers.Main) { - loadConfig(config); - } - } - catch(ex: SerializationException) { + config = SourcePluginConfig.fromJson(configJson, url); + } catch(ex: SerializationException) { Logger.e(TAG, "Failed decode config", ex); withContext(Dispatchers.Main) { UIDialogs.showDialog(this@AddSourceActivity, R.drawable.ic_error, "Invalid Config Format", null, null, 0, UIDialogs.Action("Ok", { finish() }, UIDialogs.ActionStyle.PRIMARY)); }; - } - catch(ex: Exception) { + return@launch; + } catch(ex: Exception) { Logger.e(TAG, "Failed fetch config", ex); withContext(Dispatchers.Main) { UIDialogs.showGeneralErrorDialog(this@AddSourceActivity, "Failed to fetch configuration", ex); }; + return@launch; + } + + val script: String? + try { + val scriptResp = _client.get(config.absoluteScriptUrl); + if (!scriptResp.isOk) + throw IllegalStateException("script not available [${scriptResp.code}]"); + script = scriptResp.body?.string(); + if (script.isNullOrEmpty()) + throw IllegalStateException("script empty"); + } catch (ex: Exception) { + Logger.e(TAG, "Failed fetch script", ex); + withContext(Dispatchers.Main) { + UIDialogs.showGeneralErrorDialog(this@AddSourceActivity, "Failed to fetch script", ex); + }; + return@launch; + } + + withContext(Dispatchers.Main) { + loadConfig(config, script); } }; } - fun loadConfig(config: SourcePluginConfig) { + private fun loadConfig(config: SourcePluginConfig, script: String) { _config = config; + _script = script; - _sourceHeader.loadConfig(config); + _sourceHeader.loadConfig(config, script); _sourcePermissions.removeAllViews(); _sourceWarnings.removeAllViews(); @@ -171,7 +191,7 @@ class AddSourceActivity : AppCompatActivity() { val pastelRed = resources.getColor(R.color.pastel_red); - for(warning in config.getWarnings()) + for(warning in config.getWarnings(script)) _sourceWarnings.addView( SourceInfoView(this, R.drawable.ic_security_pred, @@ -182,8 +202,8 @@ class AddSourceActivity : AppCompatActivity() { setLoading(false); } - fun install(config: SourcePluginConfig) { - StatePlugins.instance.installPlugin(this, lifecycleScope, config) { + fun install(config: SourcePluginConfig, script: String) { + StatePlugins.instance.installPlugin(this, lifecycleScope, config, script) { if(it) backToSources(); } diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/SourcePluginConfig.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/SourcePluginConfig.kt index 1d9bd749..d1b61102 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/SourcePluginConfig.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/SourcePluginConfig.kt @@ -4,6 +4,7 @@ import android.net.Uri import com.futo.platformplayer.SignatureProvider import com.futo.platformplayer.api.media.Serializer import com.futo.platformplayer.engine.IV8PluginConfig +import com.futo.platformplayer.states.StatePlugins import kotlinx.serialization.decodeFromString import java.net.URL import java.util.* @@ -78,6 +79,15 @@ class SourcePluginConfig( fun getWarnings(scriptToCheck: String? = null) : List> { val list = mutableListOf>(); + val currentlyInstalledPlugin = StatePlugins.instance.getPlugin(id); + if (currentlyInstalledPlugin != null) { + if (currentlyInstalledPlugin.config.scriptPublicKey != scriptPublicKey) { + list.add(Pair( + "Different Author", + "This plugin was signed by a different author. Please ensure that this is correct and that the plugin was not provided by a malicious actor.")); + } + } + if(scriptPublicKey.isNullOrEmpty() || scriptSignature.isNullOrEmpty()) list.add(Pair( "Missing Signature", diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/SourceDetailFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/SourceDetailFragment.kt index d7ec2197..4c7f6deb 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/SourceDetailFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/SourceDetailFragment.kt @@ -185,7 +185,7 @@ class SourceDetailFragment : MainFragment() { val config = _config; if (config != null) { - _sourceHeader.loadConfig(config); + _sourceHeader.loadConfig(config, StatePlugins.instance.getScript(config.id)); } else { _sourceHeader.clear(); } diff --git a/app/src/main/java/com/futo/platformplayer/states/StatePlugins.kt b/app/src/main/java/com/futo/platformplayer/states/StatePlugins.kt index e804a31a..24682573 100644 --- a/app/src/main/java/com/futo/platformplayer/states/StatePlugins.kt +++ b/app/src/main/java/com/futo/platformplayer/states/StatePlugins.kt @@ -177,18 +177,16 @@ class StatePlugins { } fun installPlugin(context: Context, scope: CoroutineScope, sourceUrl: String, handler: ((Boolean) -> Unit)? = null) { scope.launch(Dispatchers.IO) { + val client = ManagedHttpClient(); + val config: SourcePluginConfig; try { - val configResp = ManagedHttpClient().get(sourceUrl); + val configResp = client.get(sourceUrl); if(!configResp.isOk) throw IllegalStateException("Failed request with ${configResp.code}"); val configJson = configResp.body?.string(); if(configJson.isNullOrEmpty()) throw IllegalStateException("No response"); - val config = SourcePluginConfig.fromJson(configJson, sourceUrl); - - withContext(Dispatchers.Main) { - installPlugin(context, scope, config, handler); - } + config = SourcePluginConfig.fromJson(configJson, sourceUrl); } catch(ex: SerializationException) { Logger.e(TAG, "Failed decode config", ex); @@ -199,8 +197,8 @@ class StatePlugins { finish(); handler?.invoke(false); }, UIDialogs.ActionStyle.PRIMARY)); - }; + return@launch; } catch(ex: Exception) { Logger.e(TAG, "Failed fetch config", ex); @@ -209,13 +207,36 @@ class StatePlugins { handler?.invoke(false); }); }; + return@launch; + } + + val script: String? + try { + val scriptResp = client.get(config.absoluteScriptUrl); + if (!scriptResp.isOk) + throw IllegalStateException("script not available [${scriptResp.code}]"); + script = scriptResp.body?.string(); + if (script.isNullOrEmpty()) + throw IllegalStateException("script empty"); + } catch (ex: Exception) { + Logger.e(TAG, "Failed fetch script", ex); + withContext(Dispatchers.Main) { + UIDialogs.showGeneralErrorDialog(context, "Failed to fetch script", ex); + }; + return@launch; + } + + withContext(Dispatchers.Main) { + installPlugin(context, scope, config, script, handler); } } } - fun installPlugin(context: Context, scope: CoroutineScope, config: SourcePluginConfig, handler: ((Boolean)->Unit)? = null) { + fun installPlugin(context: Context, scope: CoroutineScope, config: SourcePluginConfig, script: String, handler: ((Boolean)->Unit)? = null) { val client = ManagedHttpClient(); val warnings = config.getWarnings(); + if (script.isEmpty()) + throw IllegalStateException("script empty"); fun doInstall(reinstall: Boolean) { UIDialogs.showDialogProgress(context) { @@ -224,13 +245,6 @@ class StatePlugins { scope.launch(Dispatchers.IO) { try { - val scriptResp = client.get(config.absoluteScriptUrl); - if (!scriptResp.isOk) - throw IllegalStateException("script not available [${scriptResp.code}]"); - val script = scriptResp.body?.string(); - if (script.isNullOrEmpty()) - throw IllegalStateException("script empty"); - withContext(Dispatchers.Main) { it.setText("Validating script..."); it.setProgress(0.25); diff --git a/app/src/main/java/com/futo/platformplayer/views/sources/SourceHeaderView.kt b/app/src/main/java/com/futo/platformplayer/views/sources/SourceHeaderView.kt index 39ee62cc..b3f63c76 100644 --- a/app/src/main/java/com/futo/platformplayer/views/sources/SourceHeaderView.kt +++ b/app/src/main/java/com/futo/platformplayer/views/sources/SourceHeaderView.kt @@ -55,7 +55,7 @@ class SourceHeaderView : LinearLayout { }; } - fun loadConfig(config: SourcePluginConfig) { + fun loadConfig(config: SourcePluginConfig, script: String?) { _config = config; val loadedIcon = StatePlugins.instance.getPluginIconOrNull(config.id); @@ -80,8 +80,10 @@ class SourceHeaderView : LinearLayout { _sourceBy.setTextColor(Color.WHITE); if (!config.scriptPublicKey.isNullOrEmpty() && !config.scriptSignature.isNullOrEmpty()) { - val script = StatePlugins.instance.getScript(config.id); - if (script != null && config.validate(script)) { + if (script == null) { + _sourceSignature.setTextColor(Color.rgb(0xAC, 0xAC, 0xAC)); + _sourceSignature.text = "Script is not available"; + } else if (config.validate(script)) { _sourceSignature.setTextColor(Color.rgb(0, 255, 0)); _sourceSignature.text = "Signature is valid"; } else {