模块作用域调整,新增__dirname,__filename
This commit is contained in:
@@ -5,6 +5,7 @@ import android.view.View
|
|||||||
import org.autojs.autojs.core.automator.UiObjectCollection
|
import org.autojs.autojs.core.automator.UiObjectCollection
|
||||||
import org.autojs.autojs.core.ui.ViewExtras
|
import org.autojs.autojs.core.ui.ViewExtras
|
||||||
import org.autojs.autojs.engine.module.AssetAndUrlModuleSourceProvider
|
import org.autojs.autojs.engine.module.AssetAndUrlModuleSourceProvider
|
||||||
|
import org.autojs.autojs.engine.module.ScopeRequire
|
||||||
import org.autojs.autojs.execution.ExecutionConfig
|
import org.autojs.autojs.execution.ExecutionConfig
|
||||||
import org.autojs.autojs.pio.UncheckedIOException
|
import org.autojs.autojs.pio.UncheckedIOException
|
||||||
import org.autojs.autojs.project.ScriptConfig
|
import org.autojs.autojs.project.ScriptConfig
|
||||||
@@ -121,11 +122,7 @@ open class RhinoJavaScriptEngine(private val mAndroidContext: android.content.Co
|
|||||||
mAndroidContext, MODULES_PATH,
|
mAndroidContext, MODULES_PATH,
|
||||||
listOf<URI>(File("/").toURI())
|
listOf<URI>(File("/").toURI())
|
||||||
)
|
)
|
||||||
RequireBuilder()
|
ScopeRequire(context, scope,SoftCachingModuleScriptProvider(provider)).install(scope)
|
||||||
.setModuleScriptProvider(SoftCachingModuleScriptProvider(provider))
|
|
||||||
.setSandboxed(true)
|
|
||||||
.createRequire(context, scope)
|
|
||||||
.install(scope)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected fun createScope(context: Context): TopLevelScope {
|
protected fun createScope(context: Context): TopLevelScope {
|
||||||
|
|||||||
@@ -1,64 +0,0 @@
|
|||||||
package org.autojs.autojs.engine.module;
|
|
||||||
|
|
||||||
import android.content.res.AssetManager;
|
|
||||||
|
|
||||||
import org.autojs.autojs.engine.encryption.ScriptEncryption;
|
|
||||||
import org.autojs.autojs.script.EncryptedScriptFileHeader;
|
|
||||||
import org.mozilla.javascript.commonjs.module.provider.ModuleSource;
|
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.FileNotFoundException;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.io.Reader;
|
|
||||||
import java.net.URI;
|
|
||||||
import java.net.URISyntaxException;
|
|
||||||
import java.net.URLConnection;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by Stardust on 2017/5/9.
|
|
||||||
*/
|
|
||||||
public class AssetAndUrlModuleSourceProvider extends UrlModuleSourceProvider {
|
|
||||||
|
|
||||||
private final android.content.Context mContext;
|
|
||||||
private final URI mBaseURI;
|
|
||||||
private final String mAssetDirPath;
|
|
||||||
private final AssetManager mAssetManager;
|
|
||||||
|
|
||||||
public AssetAndUrlModuleSourceProvider(android.content.Context context, String assetDirPath, List<URI> list) {
|
|
||||||
super(list, null);
|
|
||||||
mContext = context;
|
|
||||||
mAssetDirPath = assetDirPath;
|
|
||||||
mBaseURI = URI.create("file:///android_asset/" + assetDirPath);
|
|
||||||
mAssetManager = mContext.getAssets();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected ModuleSource loadFromPrivilegedLocations(String moduleId, Object validator) throws IOException, URISyntaxException {
|
|
||||||
String moduleIdWithExtension = moduleId;
|
|
||||||
if (!moduleIdWithExtension.endsWith(".js")) {
|
|
||||||
moduleIdWithExtension += ".js";
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
return new ModuleSource(new InputStreamReader(mAssetManager.open(mAssetDirPath + "/" + moduleIdWithExtension)), null,
|
|
||||||
new URI(mBaseURI.toString() + "/" + moduleIdWithExtension), mBaseURI, validator);
|
|
||||||
} catch (FileNotFoundException e) {
|
|
||||||
return super.loadFromPrivilegedLocations(moduleId, validator);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Reader getReader(URLConnection urlConnection) throws IOException {
|
|
||||||
InputStream stream = urlConnection.getInputStream();
|
|
||||||
byte[] bytes = new byte[stream.available()];
|
|
||||||
stream.read(bytes);
|
|
||||||
stream.close();
|
|
||||||
if (EncryptedScriptFileHeader.isValidFile(bytes)) {
|
|
||||||
byte[] clearText = ScriptEncryption.decrypt(bytes, EncryptedScriptFileHeader.BLOCK_SIZE, bytes.length);
|
|
||||||
return new InputStreamReader(new ByteArrayInputStream(clearText));
|
|
||||||
}
|
|
||||||
return new InputStreamReader(new ByteArrayInputStream(bytes));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,141 @@
|
|||||||
|
package org.autojs.autojs.engine.module
|
||||||
|
|
||||||
|
import android.content.ContentResolver
|
||||||
|
import android.content.Context
|
||||||
|
import android.net.Uri
|
||||||
|
import com.google.gson.Gson
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
import okhttp3.Request
|
||||||
|
import org.mozilla.javascript.commonjs.module.provider.ModuleSource
|
||||||
|
import org.mozilla.javascript.commonjs.module.provider.ModuleSourceProviderBase
|
||||||
|
import java.io.File
|
||||||
|
import java.io.InputStream
|
||||||
|
import java.io.InputStreamReader
|
||||||
|
import java.net.URI
|
||||||
|
|
||||||
|
class AssetAndUrlModuleSourceProvider(
|
||||||
|
context: Context,
|
||||||
|
assetDirPath: String,
|
||||||
|
list: List<URI>? = null
|
||||||
|
) : ModuleSourceProviderBase() {
|
||||||
|
val mContext = context
|
||||||
|
private val okHttpClient = OkHttpClient.Builder().followRedirects(true).build()
|
||||||
|
private val contentResolver: ContentResolver = context.contentResolver
|
||||||
|
private val moduleSources: ArrayList<URI> = arrayListOf(mBaseURI, npmModuleSource)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val mBaseURI: URI = URI.create("file:/android_asset/modules")
|
||||||
|
val npmModuleSource:URI = URI.create("file:/android_asset/modules/npm")
|
||||||
|
}
|
||||||
|
//初始化脚本以及启动文件只会从此方法加载模块,子模块加载没有以"./"或"../"开头的模块也会从此方法加载
|
||||||
|
override fun loadFromPrivilegedLocations(moduleId: String, validator: Any?): ModuleSource? {
|
||||||
|
//println("加载私有模块:$moduleId")
|
||||||
|
val uri = if (moduleId.startsWith("/")) {
|
||||||
|
File(moduleId).toURI()
|
||||||
|
} else if (moduleId.startsWith("http://") || moduleId.startsWith("https://")) {
|
||||||
|
URI.create(moduleId)
|
||||||
|
} else null
|
||||||
|
if (uri != null) {
|
||||||
|
return loadFromUri(uri, File(uri.path).parentFile?.toURI(), validator)
|
||||||
|
}
|
||||||
|
for (baseUri in moduleSources) {
|
||||||
|
val sourceUri = URI.create("$baseUri/$moduleId")
|
||||||
|
val moduleSource = loadFromUri(sourceUri, baseUri, validator)
|
||||||
|
if (moduleSource != null) {
|
||||||
|
return moduleSource
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
//这里处理node_module目录的模块
|
||||||
|
override fun loadFromFallbackLocations(moduleId: String, validator: Any?): ModuleSource? {
|
||||||
|
return super.loadFromFallbackLocations(moduleId, validator)
|
||||||
|
}
|
||||||
|
|
||||||
|
//子模块以相对路径加载时调用此方法
|
||||||
|
override fun loadFromUri(uri: URI, base: URI?, validator: Any?): ModuleSource? {
|
||||||
|
var uri = uri
|
||||||
|
if (uri.scheme == null) uri = File(uri.path).toURI()
|
||||||
|
//println("加载模块:$uri")
|
||||||
|
if (uri.scheme == "http" || uri.scheme == "https") {
|
||||||
|
return loadFromHttp(uri, base, validator)
|
||||||
|
}
|
||||||
|
val moduleSource = loadAt(uri, base, validator) ?: loadAt(
|
||||||
|
File(uri.path + ".js").toURI(), base, validator
|
||||||
|
)
|
||||||
|
if (moduleSource != null) {
|
||||||
|
return moduleSource
|
||||||
|
}
|
||||||
|
//尝试从目录加载
|
||||||
|
//尝试读取package.json指定的文件
|
||||||
|
val mainFile: URI? = try {
|
||||||
|
val packageFile = File(uri.path, "package.json")
|
||||||
|
val json = Gson().fromJson<Map<String, Any>>(
|
||||||
|
InputStreamReader(packageFile.inputStream()),
|
||||||
|
Map::class.java
|
||||||
|
)
|
||||||
|
val main = json["main"] as String?
|
||||||
|
main?.let {
|
||||||
|
packageFile.toURI().resolve(main)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
val main: URI = mainFile ?: File(uri.path, "index.js").toURI()
|
||||||
|
return loadAt(main, uri, validator)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadAt(uri: URI, base: URI?, validator: Any?): ModuleSource? {
|
||||||
|
if (uri.scheme == "http" || uri.scheme == "https") {
|
||||||
|
return loadFromHttp(uri, base, validator)
|
||||||
|
}
|
||||||
|
return try {
|
||||||
|
val inputStream = if (uri.path.startsWith("/android_asset/")) {
|
||||||
|
mContext.assets.open(uri.path.replace("/android_asset/", ""))
|
||||||
|
} else contentResolver.openInputStream(Uri.parse(uri.toString()))
|
||||||
|
if (inputStream != null) {
|
||||||
|
createModuleSource(inputStream, uri, base, validator)
|
||||||
|
} else null
|
||||||
|
} catch (e: Exception) {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadFromHttp(uri: URI, base: URI?, validator: Any?): ModuleSource? {
|
||||||
|
return try {
|
||||||
|
Request.Builder().url(uri.toString()).build().let { request ->
|
||||||
|
val response = okHttpClient.newCall(request).execute()
|
||||||
|
if (!response.isSuccessful) {
|
||||||
|
response.close()
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
response.body?.let {
|
||||||
|
val charset = it.contentType()?.charset()?.toString() ?: "utf-8"
|
||||||
|
return createModuleSource(it.byteStream(), uri, base, validator, charset)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createModuleSource(
|
||||||
|
inputStream: InputStream,
|
||||||
|
uri: URI,
|
||||||
|
base: URI?,
|
||||||
|
validator: Any?,
|
||||||
|
charset: String? = null
|
||||||
|
): ModuleSource {
|
||||||
|
val id = if (uri.scheme == "file") {
|
||||||
|
URI.create(uri.path)
|
||||||
|
} else uri
|
||||||
|
return ModuleSource(
|
||||||
|
InputStreamReader(inputStream, charset ?: "utf-8"),
|
||||||
|
null,
|
||||||
|
id,
|
||||||
|
base,
|
||||||
|
validator
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,262 @@
|
|||||||
|
package org.autojs.autojs.engine.module
|
||||||
|
|
||||||
|
import org.mozilla.javascript.*
|
||||||
|
import org.mozilla.javascript.commonjs.module.ModuleScope
|
||||||
|
import org.mozilla.javascript.commonjs.module.ModuleScript
|
||||||
|
import org.mozilla.javascript.commonjs.module.ModuleScriptProvider
|
||||||
|
|
||||||
|
import java.io.File
|
||||||
|
import java.net.URI
|
||||||
|
import java.net.URISyntaxException
|
||||||
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
|
||||||
|
|
||||||
|
open class ScopeRequire(
|
||||||
|
cx: Context, private val nativeScope: Scriptable,
|
||||||
|
private val moduleScriptProvider: ModuleScriptProvider, private val preExec: Script?,
|
||||||
|
private val postExec: Script?, private val sandboxed: Boolean = true
|
||||||
|
) : BaseFunction() {
|
||||||
|
private var paths: Scriptable? = null
|
||||||
|
private var mainModuleId: String? = null
|
||||||
|
private var mainExports: Scriptable? = null
|
||||||
|
|
||||||
|
// Modules that completed loading; visible to all threads
|
||||||
|
private val exportedModuleInterfaces: MutableMap<String, Scriptable?> = ConcurrentHashMap()
|
||||||
|
private val loadLock = Any()
|
||||||
|
|
||||||
|
constructor(cx: Context, nativeScope: Scriptable, moduleScriptProvider: ModuleScriptProvider)
|
||||||
|
: this(cx, nativeScope, moduleScriptProvider, null, null, false)
|
||||||
|
|
||||||
|
init {
|
||||||
|
prototype = getFunctionPrototype(nativeScope)
|
||||||
|
if (!sandboxed) {
|
||||||
|
paths = cx.newArray(nativeScope, 0)
|
||||||
|
defineReadOnlyProperty(this, "paths", paths)
|
||||||
|
} else paths = null
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun requireMain(cx: Context, mainModuleId: String): Scriptable? {
|
||||||
|
if (this.mainModuleId != null) {
|
||||||
|
if (this.mainModuleId != mainModuleId) {
|
||||||
|
throw IllegalStateException("Main module already set to " + this.mainModuleId)
|
||||||
|
}
|
||||||
|
return mainExports
|
||||||
|
}
|
||||||
|
val moduleScript: ModuleScript? = try {
|
||||||
|
moduleScriptProvider.getModuleScript(cx, mainModuleId, null, null, paths)
|
||||||
|
} catch (x: RuntimeException) {
|
||||||
|
throw x
|
||||||
|
} catch (x: Exception) {
|
||||||
|
throw RuntimeException(x)
|
||||||
|
}
|
||||||
|
if (moduleScript != null) {
|
||||||
|
mainExports = getExportedModuleInterface(
|
||||||
|
cx, mainModuleId,
|
||||||
|
null, null, true
|
||||||
|
)
|
||||||
|
} else if (!sandboxed) {
|
||||||
|
var mainUri: URI? = try {
|
||||||
|
URI(mainModuleId)
|
||||||
|
} catch (_: URISyntaxException) {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
if (mainUri == null || !mainUri.isAbsolute) {
|
||||||
|
val file = File(mainModuleId)
|
||||||
|
if (!file.isFile) {
|
||||||
|
throw ScriptRuntime.throwError(
|
||||||
|
cx, nativeScope,
|
||||||
|
"Module \"$mainModuleId\" not found."
|
||||||
|
)
|
||||||
|
}
|
||||||
|
mainUri = file.toURI()
|
||||||
|
}
|
||||||
|
mainExports = getExportedModuleInterface(
|
||||||
|
cx, mainUri.toString(),
|
||||||
|
mainUri, null, true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
this.mainModuleId = mainModuleId
|
||||||
|
return mainExports
|
||||||
|
}
|
||||||
|
|
||||||
|
fun install(scope: Scriptable?) {
|
||||||
|
putProperty(scope, "require", this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun call(cx: Context, scope: Scriptable, thisObj: Scriptable, args: Array<Any>?): Any {
|
||||||
|
if (args == null || args.isEmpty()) {
|
||||||
|
throw ScriptRuntime.throwError(
|
||||||
|
cx, scope,
|
||||||
|
"require() needs one argument"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
var id = Context.jsToJava(args[0], String::class.java) as String
|
||||||
|
var uri: URI? = null
|
||||||
|
var base: URI? = null
|
||||||
|
if (id.startsWith("./") || id.startsWith("../")) {
|
||||||
|
if (thisObj !is ModuleScope) {
|
||||||
|
throw ScriptRuntime.throwError(
|
||||||
|
cx, scope,
|
||||||
|
"Can't resolve relative module ID \"" + id +
|
||||||
|
"\" when require() is used outside of a module"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
base = thisObj.base
|
||||||
|
val current = thisObj.uri
|
||||||
|
uri = current.resolve(id)
|
||||||
|
if (base == null) {
|
||||||
|
id = uri.toString()
|
||||||
|
} else {
|
||||||
|
id = base.relativize(current).resolve(id).toString()
|
||||||
|
if (id[0] == '.') {
|
||||||
|
if (sandboxed) {
|
||||||
|
throw ScriptRuntime.throwError(
|
||||||
|
cx, scope,
|
||||||
|
"Module \"$id\" is not contained in sandbox."
|
||||||
|
)
|
||||||
|
}
|
||||||
|
id = uri.toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (getExportedModuleInterface(cx, id, uri, base, false))!!
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun construct(cx: Context, scope: Scriptable, args: Array<Any>): Scriptable {
|
||||||
|
throw ScriptRuntime.throwError(
|
||||||
|
cx, scope,
|
||||||
|
"require() can not be invoked as a constructor"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getExportedModuleInterface(
|
||||||
|
cx: Context, id: String, uri: URI?, base: URI?, isMain: Boolean
|
||||||
|
): Scriptable? {
|
||||||
|
// Check if the requested module is already completely loaded
|
||||||
|
var exports = exportedModuleInterfaces[id]
|
||||||
|
if (exports != null) {
|
||||||
|
if (isMain) {
|
||||||
|
throw IllegalStateException("Attempt to set main module after it was loaded")
|
||||||
|
} else
|
||||||
|
return exports
|
||||||
|
}
|
||||||
|
var threadLoadingModules: MutableMap<String, Scriptable>? =
|
||||||
|
loadingModuleInterfaces.get() as? MutableMap<String, Scriptable>
|
||||||
|
exports = threadLoadingModules?.get(id)
|
||||||
|
if (exports != null) return exports
|
||||||
|
|
||||||
|
synchronized(loadLock) {
|
||||||
|
exports = exportedModuleInterfaces[id]
|
||||||
|
if (exports != null) return exports
|
||||||
|
|
||||||
|
val moduleScript: ModuleScript = getModule(cx, id, uri, base)
|
||||||
|
if (sandboxed && !moduleScript.isSandboxed) {
|
||||||
|
throw ScriptRuntime.throwError(
|
||||||
|
cx, nativeScope, ("Module \"$id\" is not contained in sandbox.")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
exports = cx.newObject(nativeScope)
|
||||||
|
val outermostLocked: Boolean = threadLoadingModules == null
|
||||||
|
if (outermostLocked) {
|
||||||
|
threadLoadingModules = HashMap()
|
||||||
|
loadingModuleInterfaces.set(threadLoadingModules)
|
||||||
|
}
|
||||||
|
|
||||||
|
threadLoadingModules?.set(id, exports!!)
|
||||||
|
try {
|
||||||
|
val newExports: Scriptable = executeModuleScript(
|
||||||
|
cx, id, exports,
|
||||||
|
moduleScript, isMain
|
||||||
|
)
|
||||||
|
if (exports !== newExports) {
|
||||||
|
threadLoadingModules?.put(id, newExports)
|
||||||
|
exports = newExports
|
||||||
|
}
|
||||||
|
} catch (e: RuntimeException) {
|
||||||
|
threadLoadingModules?.remove(id)
|
||||||
|
throw e
|
||||||
|
} finally {
|
||||||
|
if (outermostLocked) {
|
||||||
|
exportedModuleInterfaces.putAll((threadLoadingModules!!))
|
||||||
|
loadingModuleInterfaces.set(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return exports
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun executeModuleScript(
|
||||||
|
cx: Context, id: String,
|
||||||
|
exports: Scriptable?, moduleScript: ModuleScript, isMain: Boolean
|
||||||
|
): Scriptable {
|
||||||
|
val moduleObject = cx.newObject(nativeScope) as ScriptableObject
|
||||||
|
val uri = moduleScript.uri
|
||||||
|
val base = moduleScript.base
|
||||||
|
defineReadOnlyProperty(moduleObject, "id", id)
|
||||||
|
if (!sandboxed) {
|
||||||
|
defineReadOnlyProperty(moduleObject, "uri", uri.toString())
|
||||||
|
}
|
||||||
|
val executionScope: Scriptable = ModuleScope(nativeScope, uri, base)
|
||||||
|
executionScope.put("__filename", executionScope, File(uri.path).path)
|
||||||
|
executionScope.put("__dirname", executionScope, File(uri.path).parent)
|
||||||
|
executionScope.put("exports", executionScope, exports)
|
||||||
|
executionScope.put("module", executionScope, moduleObject)
|
||||||
|
moduleObject.put("exports", moduleObject, exports)
|
||||||
|
install(executionScope)
|
||||||
|
if (isMain) {
|
||||||
|
defineReadOnlyProperty(this, "main", moduleObject)
|
||||||
|
}
|
||||||
|
//创建新作用域
|
||||||
|
val funScope = cx.newObject(executionScope)
|
||||||
|
funScope.parentScope = executionScope
|
||||||
|
|
||||||
|
executeOptionalScript(preExec, cx, funScope)
|
||||||
|
moduleScript.script.exec(cx, funScope)
|
||||||
|
executeOptionalScript(postExec, cx, funScope)
|
||||||
|
return ScriptRuntime.toObject(
|
||||||
|
cx, nativeScope,
|
||||||
|
getProperty(moduleObject, "exports")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getModule(cx: Context, id: String, uri: URI?, base: URI?): ModuleScript {
|
||||||
|
try {
|
||||||
|
return moduleScriptProvider.getModuleScript(cx, id, uri, base, paths)
|
||||||
|
?: throw ScriptRuntime.throwError(
|
||||||
|
cx, nativeScope, ("Module \"$id\" not found.")
|
||||||
|
)
|
||||||
|
} catch (e: RuntimeException) {
|
||||||
|
throw e
|
||||||
|
} catch (e: Exception) {
|
||||||
|
throw Context.throwAsScriptRuntimeEx(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getFunctionName() = "require"
|
||||||
|
override fun getArity() = 1
|
||||||
|
override fun getLength() = 1
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val serialVersionUID = 1L
|
||||||
|
|
||||||
|
private val loadingModuleInterfaces = ThreadLocal<Map<String, Scriptable>>()
|
||||||
|
private fun executeOptionalScript(
|
||||||
|
script: Script?, cx: Context,
|
||||||
|
executionScope: Scriptable
|
||||||
|
) {
|
||||||
|
script?.exec(cx, executionScope)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun defineReadOnlyProperty(
|
||||||
|
obj: ScriptableObject,
|
||||||
|
name: String, value: Any?
|
||||||
|
) {
|
||||||
|
putProperty(obj, name, value)
|
||||||
|
obj.setAttributes(
|
||||||
|
name, READONLY or
|
||||||
|
PERMANENT
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,360 +0,0 @@
|
|||||||
package org.autojs.autojs.engine.module;
|
|
||||||
|
|
||||||
import org.mozilla.javascript.commonjs.module.provider.DefaultUrlConnectionExpiryCalculator;
|
|
||||||
import org.mozilla.javascript.commonjs.module.provider.ModuleSource;
|
|
||||||
import org.mozilla.javascript.commonjs.module.provider.ModuleSourceProviderBase;
|
|
||||||
import org.mozilla.javascript.commonjs.module.provider.ParsedContentType;
|
|
||||||
import org.mozilla.javascript.commonjs.module.provider.UrlConnectionExpiryCalculator;
|
|
||||||
import org.mozilla.javascript.commonjs.module.provider.UrlConnectionSecurityDomainProvider;
|
|
||||||
|
|
||||||
import java.io.FileNotFoundException;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.io.Reader;
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.net.HttpURLConnection;
|
|
||||||
import java.net.URI;
|
|
||||||
import java.net.URISyntaxException;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.net.URLConnection;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A URL-based script provider that can load modules against a set of base
|
|
||||||
* privileged and fallback URIs. It is deliberately not named "URI provider"
|
|
||||||
* but a "URL provider" since it actually only works against those URIs that
|
|
||||||
* are URLs (and the JRE has a protocol handler for them). It creates cache
|
|
||||||
* validators that are suitable for use with both file: and http: URL
|
|
||||||
* protocols. Specifically, it is able to use both last-modified timestamps and
|
|
||||||
* ETags for cache revalidation, and follows the HTTP cache expiry calculation
|
|
||||||
* model, and allows for fallback heuristic expiry calculation when no server
|
|
||||||
* specified expiry is provided.
|
|
||||||
*
|
|
||||||
* @author Attila Szegedi
|
|
||||||
* @version $Id: UrlModuleSourceProvider.java,v 1.4 2011/04/07 20:26:12 hannes%helma.at Exp $
|
|
||||||
*/
|
|
||||||
public class UrlModuleSourceProvider extends ModuleSourceProviderBase {
|
|
||||||
private static final long serialVersionUID = 1L;
|
|
||||||
|
|
||||||
private final Iterable<URI> privilegedUris;
|
|
||||||
private final Iterable<URI> fallbackUris;
|
|
||||||
private final UrlConnectionSecurityDomainProvider
|
|
||||||
urlConnectionSecurityDomainProvider;
|
|
||||||
private final UrlConnectionExpiryCalculator urlConnectionExpiryCalculator;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new module script provider that loads modules against a set of
|
|
||||||
* privileged and fallback URIs. It will use a fixed default cache expiry
|
|
||||||
* of 60 seconds, and provide no security domain objects for the resource.
|
|
||||||
*
|
|
||||||
* @param privilegedUris an iterable providing the privileged URIs. Can be
|
|
||||||
* null if no privileged URIs are used.
|
|
||||||
* @param fallbackUris an iterable providing the fallback URIs. Can be
|
|
||||||
* null if no fallback URIs are used.
|
|
||||||
*/
|
|
||||||
public UrlModuleSourceProvider(Iterable<URI> privilegedUris,
|
|
||||||
Iterable<URI> fallbackUris) {
|
|
||||||
this(privilegedUris, fallbackUris,
|
|
||||||
new DefaultUrlConnectionExpiryCalculator(), null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new module script provider that loads modules against a set of
|
|
||||||
* privileged and fallback URIs. It will use the specified heuristic cache
|
|
||||||
* expiry calculator and security domain provider.
|
|
||||||
*
|
|
||||||
* @param privilegedUris an iterable providing the privileged URIs. Can be
|
|
||||||
* null if no privileged URIs are used.
|
|
||||||
* @param fallbackUris an iterable providing the fallback URIs. Can be
|
|
||||||
* null if no fallback URIs are used.
|
|
||||||
* @param urlConnectionExpiryCalculator the calculator object for heuristic
|
|
||||||
* calculation of the resource expiry, used when no expiry is provided by
|
|
||||||
* the server of the resource. Can be null, in which case the maximum age
|
|
||||||
* of cached entries without validation will be zero.
|
|
||||||
* @param urlConnectionSecurityDomainProvider object that provides security
|
|
||||||
* domain objects for the loaded sources. Can be null, in which case the
|
|
||||||
* loaded sources will have no security domain associated with them.
|
|
||||||
*/
|
|
||||||
public UrlModuleSourceProvider(Iterable<URI> privilegedUris,
|
|
||||||
Iterable<URI> fallbackUris,
|
|
||||||
UrlConnectionExpiryCalculator urlConnectionExpiryCalculator,
|
|
||||||
UrlConnectionSecurityDomainProvider urlConnectionSecurityDomainProvider) {
|
|
||||||
this.privilegedUris = privilegedUris;
|
|
||||||
this.fallbackUris = fallbackUris;
|
|
||||||
this.urlConnectionExpiryCalculator = urlConnectionExpiryCalculator;
|
|
||||||
this.urlConnectionSecurityDomainProvider =
|
|
||||||
urlConnectionSecurityDomainProvider;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected ModuleSource loadFromPrivilegedLocations(
|
|
||||||
String moduleId, Object validator)
|
|
||||||
throws IOException, URISyntaxException {
|
|
||||||
return loadFromPathList(moduleId, validator, privilegedUris);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected ModuleSource loadFromFallbackLocations(
|
|
||||||
String moduleId, Object validator)
|
|
||||||
throws IOException, URISyntaxException {
|
|
||||||
return loadFromPathList(moduleId, validator, fallbackUris);
|
|
||||||
}
|
|
||||||
|
|
||||||
private ModuleSource loadFromPathList(String moduleId,
|
|
||||||
Object validator, Iterable<URI> paths)
|
|
||||||
throws IOException, URISyntaxException {
|
|
||||||
if (paths == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
for (URI path : paths) {
|
|
||||||
final ModuleSource moduleSource = loadFromUri(
|
|
||||||
path.resolve(moduleId), path, validator);
|
|
||||||
if (moduleSource != null) {
|
|
||||||
return moduleSource;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected ModuleSource loadFromUri(URI uri, URI base, Object validator)
|
|
||||||
throws IOException, URISyntaxException {
|
|
||||||
// We expect modules to have a ".js" file name extension ...
|
|
||||||
URI fullUri = new URI(uri + ".js");
|
|
||||||
ModuleSource source = loadFromActualUri(fullUri, base, validator);
|
|
||||||
// ... but for compatibility we support modules without extension,
|
|
||||||
// or ids with explicit extension.
|
|
||||||
return source != null ?
|
|
||||||
source : loadFromActualUri(uri, base, validator);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected ModuleSource loadFromActualUri(URI uri, URI base, Object validator)
|
|
||||||
throws IOException {
|
|
||||||
final URL url = new URL(base == null ? null : base.toURL(), uri.toString());
|
|
||||||
final long request_time = System.currentTimeMillis();
|
|
||||||
final URLConnection urlConnection = openUrlConnection(url);
|
|
||||||
final URLValidator applicableValidator;
|
|
||||||
if (validator instanceof final URLValidator uriValidator) {
|
|
||||||
applicableValidator = uriValidator.appliesTo(uri) ? uriValidator :
|
|
||||||
null;
|
|
||||||
} else {
|
|
||||||
applicableValidator = null;
|
|
||||||
}
|
|
||||||
if (applicableValidator != null) {
|
|
||||||
applicableValidator.applyConditionals(urlConnection);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
urlConnection.connect();
|
|
||||||
if (applicableValidator != null &&
|
|
||||||
applicableValidator.updateValidator(urlConnection,
|
|
||||||
request_time, urlConnectionExpiryCalculator)) {
|
|
||||||
close(urlConnection);
|
|
||||||
return NOT_MODIFIED;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new ModuleSource(getReader(urlConnection),
|
|
||||||
getSecurityDomain(urlConnection), uri, base,
|
|
||||||
new URLValidator(uri, urlConnection, request_time,
|
|
||||||
urlConnectionExpiryCalculator));
|
|
||||||
} catch (FileNotFoundException e) {
|
|
||||||
return null;
|
|
||||||
} catch (RuntimeException | IOException e) {
|
|
||||||
close(urlConnection);
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Reader getReader(URLConnection urlConnection)
|
|
||||||
throws IOException {
|
|
||||||
return new InputStreamReader(urlConnection.getInputStream(),
|
|
||||||
getCharacterEncoding(urlConnection));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected String getCharacterEncoding(URLConnection urlConnection) {
|
|
||||||
final ParsedContentType pct = new ParsedContentType(
|
|
||||||
urlConnection.getContentType());
|
|
||||||
final String encoding = pct.getEncoding();
|
|
||||||
if (encoding != null) {
|
|
||||||
return encoding;
|
|
||||||
}
|
|
||||||
final String contentType = pct.getContentType();
|
|
||||||
if (contentType != null && contentType.startsWith("text/")) {
|
|
||||||
return "8859_1";
|
|
||||||
}
|
|
||||||
return "utf-8";
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Object getSecurityDomain(URLConnection urlConnection) {
|
|
||||||
return urlConnectionSecurityDomainProvider == null ? null :
|
|
||||||
urlConnectionSecurityDomainProvider.getSecurityDomain(
|
|
||||||
urlConnection);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void close(URLConnection urlConnection) {
|
|
||||||
try {
|
|
||||||
urlConnection.getInputStream().close();
|
|
||||||
} catch (IOException e) {
|
|
||||||
onFailedClosingUrlConnection(urlConnection, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Override if you want to get notified if the URL connection fails to
|
|
||||||
* close. Does nothing by default.
|
|
||||||
*
|
|
||||||
* @param urlConnection the connection
|
|
||||||
* @param cause the cause it failed to close.
|
|
||||||
*/
|
|
||||||
protected void onFailedClosingUrlConnection(URLConnection urlConnection,
|
|
||||||
IOException cause) {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Can be overridden in subclasses to customize the URL connection opening
|
|
||||||
* process. By default, just calls {@link URL#openConnection()}.
|
|
||||||
*
|
|
||||||
* @param url the URL
|
|
||||||
* @return a connection to the URL.
|
|
||||||
* @throws IOException if an I/O error occurs.
|
|
||||||
*/
|
|
||||||
protected URLConnection openUrlConnection(URL url) throws IOException {
|
|
||||||
return url.openConnection();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean entityNeedsRevalidation(Object validator) {
|
|
||||||
return !(validator instanceof URLValidator)
|
|
||||||
|| ((URLValidator) validator).entityNeedsRevalidation();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class URLValidator implements Serializable {
|
|
||||||
private static final long serialVersionUID = 1L;
|
|
||||||
|
|
||||||
private final URI uri;
|
|
||||||
private final long lastModified;
|
|
||||||
private final String entityTags;
|
|
||||||
private long expiry;
|
|
||||||
|
|
||||||
public URLValidator(URI uri, URLConnection urlConnection,
|
|
||||||
long request_time, UrlConnectionExpiryCalculator
|
|
||||||
urlConnectionExpiryCalculator) {
|
|
||||||
this.uri = uri;
|
|
||||||
this.lastModified = urlConnection.getLastModified();
|
|
||||||
this.entityTags = getEntityTags(urlConnection);
|
|
||||||
expiry = calculateExpiry(urlConnection, request_time,
|
|
||||||
urlConnectionExpiryCalculator);
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean updateValidator(URLConnection urlConnection, long request_time,
|
|
||||||
UrlConnectionExpiryCalculator urlConnectionExpiryCalculator)
|
|
||||||
throws IOException {
|
|
||||||
boolean isResourceChanged = isResourceChanged(urlConnection);
|
|
||||||
if (!isResourceChanged) {
|
|
||||||
expiry = calculateExpiry(urlConnection, request_time,
|
|
||||||
urlConnectionExpiryCalculator);
|
|
||||||
}
|
|
||||||
return isResourceChanged;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isResourceChanged(URLConnection urlConnection)
|
|
||||||
throws IOException {
|
|
||||||
if (urlConnection instanceof HttpURLConnection) {
|
|
||||||
return ((HttpURLConnection) urlConnection).getResponseCode() ==
|
|
||||||
HttpURLConnection.HTTP_NOT_MODIFIED;
|
|
||||||
}
|
|
||||||
return lastModified != urlConnection.getLastModified();
|
|
||||||
}
|
|
||||||
|
|
||||||
private long calculateExpiry(URLConnection urlConnection,
|
|
||||||
long request_time, UrlConnectionExpiryCalculator
|
|
||||||
urlConnectionExpiryCalculator) {
|
|
||||||
if ("no-cache".equals(urlConnection.getHeaderField("Pragma"))) {
|
|
||||||
return 0L;
|
|
||||||
}
|
|
||||||
final String cacheControl = urlConnection.getHeaderField(
|
|
||||||
"Cache-Control");
|
|
||||||
if (cacheControl != null) {
|
|
||||||
if (cacheControl.contains("no-cache")) {
|
|
||||||
return 0L;
|
|
||||||
}
|
|
||||||
final int max_age = getMaxAge(cacheControl);
|
|
||||||
if (-1 != max_age) {
|
|
||||||
final long response_time = System.currentTimeMillis();
|
|
||||||
final long apparent_age = Math.max(0, response_time -
|
|
||||||
urlConnection.getDate());
|
|
||||||
final long corrected_received_age = Math.max(apparent_age,
|
|
||||||
urlConnection.getHeaderFieldInt("Age", 0) * 1000L);
|
|
||||||
final long response_delay = response_time - request_time;
|
|
||||||
final long corrected_initial_age = corrected_received_age +
|
|
||||||
response_delay;
|
|
||||||
final long creation_time = response_time -
|
|
||||||
corrected_initial_age;
|
|
||||||
return max_age * 1000L + creation_time;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
final long explicitExpiry = urlConnection.getHeaderFieldDate(
|
|
||||||
"Expires", -1L);
|
|
||||||
if (explicitExpiry != -1L) {
|
|
||||||
return explicitExpiry;
|
|
||||||
}
|
|
||||||
return urlConnectionExpiryCalculator == null ? 0L :
|
|
||||||
urlConnectionExpiryCalculator.calculateExpiry(urlConnection);
|
|
||||||
}
|
|
||||||
|
|
||||||
private int getMaxAge(String cacheControl) {
|
|
||||||
final int maxAgeIndex = cacheControl.indexOf("max-age");
|
|
||||||
if (maxAgeIndex == -1) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
final int eq = cacheControl.indexOf('=', maxAgeIndex + 7);
|
|
||||||
if (eq == -1) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
final int comma = cacheControl.indexOf(',', eq + 1);
|
|
||||||
final String strAge;
|
|
||||||
if (comma == -1) {
|
|
||||||
strAge = cacheControl.substring(eq + 1);
|
|
||||||
} else {
|
|
||||||
strAge = cacheControl.substring(eq + 1, comma);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
return Integer.parseInt(strAge);
|
|
||||||
} catch (NumberFormatException e) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getEntityTags(URLConnection urlConnection) {
|
|
||||||
final List<String> etags = urlConnection.getHeaderFields().get("ETag");
|
|
||||||
if (etags == null || etags.isEmpty()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
final StringBuilder b = new StringBuilder();
|
|
||||||
final Iterator<String> it = etags.iterator();
|
|
||||||
b.append(it.next());
|
|
||||||
while (it.hasNext()) {
|
|
||||||
b.append(", ").append(it.next());
|
|
||||||
}
|
|
||||||
return b.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean appliesTo(URI uri) {
|
|
||||||
return this.uri.equals(uri);
|
|
||||||
}
|
|
||||||
|
|
||||||
void applyConditionals(URLConnection urlConnection) {
|
|
||||||
if (lastModified != 0L) {
|
|
||||||
urlConnection.setIfModifiedSince(lastModified);
|
|
||||||
}
|
|
||||||
if (entityTags != null && entityTags.length() > 0) {
|
|
||||||
urlConnection.addRequestProperty("If-None-Match", entityTags);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean entityNeedsRevalidation() {
|
|
||||||
return System.currentTimeMillis() > expiry;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
#Fri May 26 20:57:48 CST 2023
|
#Sat Jun 10 13:06:56 CST 2023
|
||||||
BUILD_TIME=1685105868950
|
BUILD_TIME=1686373616005
|
||||||
COMPILE_SDK_VERSION=33
|
COMPILE_SDK_VERSION=33
|
||||||
JAVA_VERSION=20
|
JAVA_VERSION=20
|
||||||
MIN_SDK_VERSION=24
|
MIN_SDK_VERSION=24
|
||||||
TARGET_SDK_VERSION=33
|
TARGET_SDK_VERSION=33
|
||||||
VERSION_BUILD=1909
|
VERSION_BUILD=1910
|
||||||
VERSION_NAME=6.3.1
|
VERSION_NAME=6.3.1
|
||||||
|
|||||||
Reference in New Issue
Block a user