Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Plugin 1.3.4 #2431

Merged
merged 14 commits into from
Feb 7, 2020
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ object BaseService {
File(Core.deviceStorage.noBackupFilesDir, "stat_main"),
File(configRoot, CONFIG_FILE),
if (udpFallback == null) "-u" else null)
check(udpFallback?.pluginPath == null) { "UDP fallback cannot have plugins" }
check(udpFallback?.plugin == null) { "UDP fallback cannot have plugins" }
udpFallback?.start(this,
File(Core.deviceStorage.noBackupFilesDir, "stat_udp"),
File(configRoot, CONFIG_FILE_UDP),
Expand Down
5 changes: 2 additions & 3 deletions core/src/main/java/com/github/shadowsocks/bg/ProxyInstance.kt
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,7 @@ import java.security.MessageDigest
class ProxyInstance(val profile: Profile, private val route: String = profile.route) {
private var configFile: File? = null
var trafficMonitor: TrafficMonitor? = null
private val plugin = PluginConfiguration(profile.plugin ?: "").selectedOptions
val pluginPath by lazy { PluginManager.init(plugin) }
val plugin by lazy { PluginManager.init(PluginConfiguration(profile.plugin ?: "")) }
private var scheduleConfigUpdate = false

suspend fun init(service: BaseService.Interface, hosts: HostsFile) {
Expand Down Expand Up @@ -115,7 +114,7 @@ class ProxyInstance(val profile: Profile, private val route: String = profile.ro

this.configFile = configFile
val config = profile.toJson()
if (pluginPath != null) config.put("plugin", pluginPath).put("plugin_opts", plugin.toString())
plugin?.let { (path, opts) -> config.put("plugin", path).put("plugin_opts", opts.toString()) }
configFile.writeText(config.toString())

val cmd = service.buildAdditionalArguments(arrayListOf(
Expand Down
4 changes: 2 additions & 2 deletions core/src/main/java/com/github/shadowsocks/database/Profile.kt
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,7 @@ data class Profile(
.encodedAuthority("$auth@$wrappedHost:$remotePort")
val configuration = PluginConfiguration(plugin ?: "")
if (configuration.selected.isNotEmpty()) {
builder.appendQueryParameter(Key.plugin, configuration.selectedOptions.toString(false))
builder.appendQueryParameter(Key.plugin, configuration.getOptions().toString(false))
}
if (!name.isNullOrEmpty()) builder.fragment(name)
return builder.build()
Expand All @@ -315,7 +315,7 @@ data class Profile(
put("password", password)
put("method", method)
if (profiles == null) return@apply
PluginConfiguration(plugin ?: "").selectedOptions.also {
PluginConfiguration(plugin ?: "").getOptions().also {
if (it.id.isNotEmpty()) {
put("plugin", it.id)
put("plugin_opts", it.toString())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,11 @@
package com.github.shadowsocks.plugin

import android.content.pm.ResolveInfo
import android.os.Bundle

class NativePlugin(resolveInfo: ResolveInfo) : ResolvedPlugin(resolveInfo) {
init {
check(resolveInfo.providerInfo != null)
}

override val metaData: Bundle get() = resolveInfo.providerInfo.metaData
override val packageName: String get() = resolveInfo.providerInfo.packageName
override val componentInfo get() = resolveInfo.providerInfo!!
}
9 changes: 9 additions & 0 deletions core/src/main/java/com/github/shadowsocks/plugin/Plugin.kt
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,18 @@ import android.graphics.drawable.Drawable

abstract class Plugin {
abstract val id: String
open val idAliases get() = emptyArray<String>()
abstract val label: CharSequence
open val icon: Drawable? get() = null
open val defaultConfig: String? get() = null
open val packageName: String get() = ""
open val trusted: Boolean get() = true
open val directBootAware: Boolean get() = true

override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
return id == (other as Plugin).id
}
override fun hashCode() = id.hashCode()
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,15 @@ class PluginConfiguration(val pluginsOptions: Map<String, PluginOptions>, val se
} else PluginOptions(line)
})

fun getOptions(id: String): PluginOptions = if (id.isEmpty()) PluginOptions() else
pluginsOptions[id] ?: PluginOptions(id, PluginManager.fetchPlugins()[id]?.defaultConfig)
val selectedOptions: PluginOptions get() = getOptions(selected)
fun getOptions(
id: String = selected,
defaultConfig: () -> String? = { PluginManager.fetchPlugins().lookup[id]?.defaultConfig }
) = if (id.isEmpty()) PluginOptions() else pluginsOptions[id] ?: PluginOptions(id, defaultConfig())

override fun toString(): String {
val result = LinkedList<PluginOptions>()
for ((id, opt) in pluginsOptions) if (id == this.selected) result.addFirst(opt) else result.addLast(opt)
if (!pluginsOptions.contains(selected)) result.addFirst(selectedOptions)
if (!pluginsOptions.contains(selected)) result.addFirst(getOptions())
return result.joinToString("\n") { it.toString(false) }
}
}
41 changes: 41 additions & 0 deletions core/src/main/java/com/github/shadowsocks/plugin/PluginList.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*******************************************************************************
* *
* Copyright (C) 2020 by Max Lv <max.c.lv@gmail.com> *
* Copyright (C) 2020 by Mygod Studio <contact-shadowsocks-android@mygod.be> *
* *
* 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 <http://www.gnu.org/licenses/>. *
* *
*******************************************************************************/

package com.github.shadowsocks.plugin

import android.content.Intent
import android.content.pm.PackageManager
import com.github.shadowsocks.Core.app

class PluginList : ArrayList<Plugin>() {
init {
add(NoPlugin)
addAll(app.packageManager.queryIntentContentProviders(
Intent(PluginContract.ACTION_NATIVE_PLUGIN), PackageManager.GET_META_DATA).map { NativePlugin(it) })
}

val lookup = mutableMapOf<String, Plugin>().apply {
for (plugin in this@PluginList) {
fun check(old: Plugin?) = check(old == null || old === plugin)
check(put(plugin.id, plugin))
for (alias in plugin.idAliases) check(put(alias, plugin))
}
}
}
45 changes: 26 additions & 19 deletions core/src/main/java/com/github/shadowsocks/plugin/PluginManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import android.annotation.SuppressLint
import android.content.BroadcastReceiver
import android.content.ContentResolver
import android.content.Intent
import android.content.pm.ComponentInfo
import android.content.pm.PackageManager
import android.content.pm.ProviderInfo
import android.content.pm.Signature
Expand Down Expand Up @@ -100,19 +101,15 @@ object PluginManager {
}

private var receiver: BroadcastReceiver? = null
private var cachedPlugins: Map<String, Plugin>? = null
fun fetchPlugins(): Map<String, Plugin> = synchronized(this) {
private var cachedPlugins: PluginList? = null
fun fetchPlugins() = synchronized(this) {
if (receiver == null) receiver = app.listenForPackageChanges {
synchronized(this) {
receiver = null
cachedPlugins = null
}
}
if (cachedPlugins == null) {
val pm = app.packageManager
cachedPlugins = (pm.queryIntentContentProviders(Intent(PluginContract.ACTION_NATIVE_PLUGIN),
PackageManager.GET_META_DATA).map { NativePlugin(it) } + NoPlugin).associateBy { it.id }
}
if (cachedPlugins == null) cachedPlugins = PluginList()
cachedPlugins!!
}

Expand All @@ -125,30 +122,34 @@ object PluginManager {

// the following parts are meant to be used by :bg
@Throws(Throwable::class)
fun init(options: PluginOptions): String? {
if (options.id.isEmpty()) return null
fun init(configuration: PluginConfiguration): Pair<String, PluginOptions>? {
if (configuration.selected.isEmpty()) return null
var throwable: Throwable? = null

try {
val path = initNative(options)
if (path != null) return path
val result = initNative(configuration)
if (result != null) return result
} catch (t: Throwable) {
if (throwable == null) throwable = t else printLog(t)
}

// add other plugin types here

throw throwable ?: PluginNotFoundException(options.id)
throw throwable ?: PluginNotFoundException(configuration.selected)
}

private fun initNative(options: PluginOptions): String? {
private fun initNative(configuration: PluginConfiguration): Pair<String, PluginOptions>? {
val providers = app.packageManager.queryIntentContentProviders(
Intent(PluginContract.ACTION_NATIVE_PLUGIN, buildUri(options.id)), PackageManager.GET_META_DATA)
Intent(PluginContract.ACTION_NATIVE_PLUGIN, buildUri(configuration.selected)),
PackageManager.GET_META_DATA or
PackageManager.MATCH_DIRECT_BOOT_UNAWARE or PackageManager.MATCH_DIRECT_BOOT_AWARE
)
if (providers.isEmpty()) return null
val provider = providers.single().providerInfo
val options = configuration.getOptions { provider.loadString(PluginContract.METADATA_KEY_DEFAULT_CONFIG) }
var failure: Throwable? = null
try {
initNativeFaster(provider)?.also { return it }
initNativeFaster(provider)?.also { return it to options }
} catch (t: Throwable) {
Crashlytics.log(Log.WARN, TAG, "Initializing native plugin faster mode failed")
failure = t
Expand All @@ -158,25 +159,24 @@ object PluginManager {
scheme(ContentResolver.SCHEME_CONTENT)
authority(provider.authority)
}.build()
val cr = app.contentResolver
try {
return initNativeFast(cr, options, uri)
return initNativeFast(app.contentResolver, options, uri)?.let { it to options }
} catch (t: Throwable) {
Crashlytics.log(Log.WARN, TAG, "Initializing native plugin fast mode failed")
failure?.also { t.addSuppressed(it) }
failure = t
}

try {
return initNativeSlow(cr, options, uri)
return initNativeSlow(app.contentResolver, options, uri)?.let { it to options }
} catch (t: Throwable) {
failure?.also { t.addSuppressed(it) }
throw t
}
}

private fun initNativeFaster(provider: ProviderInfo): String? {
return provider.metaData.getString(PluginContract.METADATA_KEY_EXECUTABLE_PATH)?.let { relativePath ->
return provider.loadString(PluginContract.METADATA_KEY_EXECUTABLE_PATH)?.let { relativePath ->
File(provider.applicationInfo.nativeLibraryDir).resolve(relativePath).apply {
check(canExecute())
}.absolutePath
Expand Down Expand Up @@ -219,4 +219,11 @@ object PluginManager {
if (!initialized) entryNotFound()
return File(pluginDir, options.id).absolutePath
}

fun ComponentInfo.loadString(key: String) = when (val value = metaData.get(key)) {
is String -> value
is Int -> app.packageManager.getResourcesForApplication(applicationInfo).getString(value)
null -> null
else -> error("meta-data $key has invalid type ${value.javaClass}")
}
}
30 changes: 23 additions & 7 deletions core/src/main/java/com/github/shadowsocks/plugin/ResolvedPlugin.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,22 +20,38 @@

package com.github.shadowsocks.plugin

import android.content.pm.ComponentInfo
import android.content.pm.ResolveInfo
import android.graphics.drawable.Drawable
import android.os.Bundle
import android.os.Build
import com.github.shadowsocks.Core
import com.github.shadowsocks.Core.app
import com.github.shadowsocks.plugin.PluginManager.loadString
import com.github.shadowsocks.utils.signaturesCompat

abstract class ResolvedPlugin(protected val resolveInfo: ResolveInfo) : Plugin() {
protected abstract val metaData: Bundle
protected abstract val componentInfo: ComponentInfo

override val id: String by lazy { metaData.getString(PluginContract.METADATA_KEY_ID)!! }
override val label: CharSequence by lazy { resolveInfo.loadLabel(app.packageManager) }
override val icon: Drawable by lazy { resolveInfo.loadIcon(app.packageManager) }
override val defaultConfig: String? by lazy { metaData.getString(PluginContract.METADATA_KEY_DEFAULT_CONFIG) }
override val packageName: String get() = resolveInfo.resolvePackageName
override val id by lazy { componentInfo.loadString(PluginContract.METADATA_KEY_ID)!! }
override val idAliases: Array<String> by lazy {
when (val value = componentInfo.metaData.get(PluginContract.METADATA_KEY_ID_ALIASES)) {
is String -> arrayOf(value)
is Int -> app.packageManager.getResourcesForApplication(componentInfo.applicationInfo).run {
when (getResourceTypeName(value)) {
"string" -> arrayOf(getString(value))
else -> getStringArray(value)
}
}
null -> emptyArray()
else -> error("unknown type for plugin meta-data idAliases")
}
}
override val label: CharSequence get() = resolveInfo.loadLabel(app.packageManager)
override val icon: Drawable get() = resolveInfo.loadIcon(app.packageManager)
override val defaultConfig by lazy { componentInfo.loadString(PluginContract.METADATA_KEY_DEFAULT_CONFIG) }
override val packageName: String get() = componentInfo.packageName
override val trusted by lazy {
Core.getPackageInfo(packageName).signaturesCompat.any(PluginManager.trustedSignatures::contains)
}
override val directBootAware get() = Build.VERSION.SDK_INT < 24 || componentInfo.directBootAware
}
1 change: 1 addition & 0 deletions core/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -160,4 +160,5 @@
<string name="plugin_unknown">Unknown plugin %s</string>
<string name="plugin_untrusted">Warning: This plugin does not seem to come from a known trusted source.</string>
<string name="profile_plugin">Plugin: %s</string>
<string name="plugin_auto_connect_unlock_only">This plugin might not work with Auto Connect</string>
</resources>
Loading