From f93aa9acfc39ea87e775e4c2b7cbe95a99f92227 Mon Sep 17 00:00:00 2001 From: aiselp Date: Sat, 10 Jun 2023 13:12:40 +0800 Subject: [PATCH] =?UTF-8?q?=E6=A8=A1=E5=9D=97=E4=BD=9C=E7=94=A8=E5=9F=9F?= =?UTF-8?q?=E8=B0=83=E6=95=B4=EF=BC=8C=E6=96=B0=E5=A2=9E=5F=5Fdirname?= =?UTF-8?q?=EF=BC=8C=5F=5Ffilename?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../autojs/engine/RhinoJavaScriptEngine.kt | 7 +- .../AssetAndUrlModuleSourceProvider.java | 64 ---- .../module/AssetAndUrlModuleSourceProvider.kt | 141 +++++++ .../autojs/engine/module/ScopeRequire.kt | 262 +++++++++++++ .../module/UrlModuleSourceProvider.java | 360 ------------------ version.properties | 6 +- 6 files changed, 408 insertions(+), 432 deletions(-) delete mode 100644 app/src/main/java/org/autojs/autojs/engine/module/AssetAndUrlModuleSourceProvider.java create mode 100644 app/src/main/java/org/autojs/autojs/engine/module/AssetAndUrlModuleSourceProvider.kt create mode 100644 app/src/main/java/org/autojs/autojs/engine/module/ScopeRequire.kt delete mode 100644 app/src/main/java/org/autojs/autojs/engine/module/UrlModuleSourceProvider.java diff --git a/app/src/main/java/org/autojs/autojs/engine/RhinoJavaScriptEngine.kt b/app/src/main/java/org/autojs/autojs/engine/RhinoJavaScriptEngine.kt index 52243e86..3adb1842 100644 --- a/app/src/main/java/org/autojs/autojs/engine/RhinoJavaScriptEngine.kt +++ b/app/src/main/java/org/autojs/autojs/engine/RhinoJavaScriptEngine.kt @@ -5,6 +5,7 @@ import android.view.View import org.autojs.autojs.core.automator.UiObjectCollection import org.autojs.autojs.core.ui.ViewExtras import org.autojs.autojs.engine.module.AssetAndUrlModuleSourceProvider +import org.autojs.autojs.engine.module.ScopeRequire import org.autojs.autojs.execution.ExecutionConfig import org.autojs.autojs.pio.UncheckedIOException import org.autojs.autojs.project.ScriptConfig @@ -121,11 +122,7 @@ open class RhinoJavaScriptEngine(private val mAndroidContext: android.content.Co mAndroidContext, MODULES_PATH, listOf(File("/").toURI()) ) - RequireBuilder() - .setModuleScriptProvider(SoftCachingModuleScriptProvider(provider)) - .setSandboxed(true) - .createRequire(context, scope) - .install(scope) + ScopeRequire(context, scope,SoftCachingModuleScriptProvider(provider)).install(scope) } protected fun createScope(context: Context): TopLevelScope { diff --git a/app/src/main/java/org/autojs/autojs/engine/module/AssetAndUrlModuleSourceProvider.java b/app/src/main/java/org/autojs/autojs/engine/module/AssetAndUrlModuleSourceProvider.java deleted file mode 100644 index fd9738fd..00000000 --- a/app/src/main/java/org/autojs/autojs/engine/module/AssetAndUrlModuleSourceProvider.java +++ /dev/null @@ -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 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)); - } -} \ No newline at end of file diff --git a/app/src/main/java/org/autojs/autojs/engine/module/AssetAndUrlModuleSourceProvider.kt b/app/src/main/java/org/autojs/autojs/engine/module/AssetAndUrlModuleSourceProvider.kt new file mode 100644 index 00000000..96dfdbcb --- /dev/null +++ b/app/src/main/java/org/autojs/autojs/engine/module/AssetAndUrlModuleSourceProvider.kt @@ -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? = null +) : ModuleSourceProviderBase() { + val mContext = context + private val okHttpClient = OkHttpClient.Builder().followRedirects(true).build() + private val contentResolver: ContentResolver = context.contentResolver + private val moduleSources: ArrayList = 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>( + 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 + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/autojs/autojs/engine/module/ScopeRequire.kt b/app/src/main/java/org/autojs/autojs/engine/module/ScopeRequire.kt new file mode 100644 index 00000000..d92620a6 --- /dev/null +++ b/app/src/main/java/org/autojs/autojs/engine/module/ScopeRequire.kt @@ -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 = 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 { + 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): 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? = + loadingModuleInterfaces.get() as? MutableMap + 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>() + 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 + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/autojs/autojs/engine/module/UrlModuleSourceProvider.java b/app/src/main/java/org/autojs/autojs/engine/module/UrlModuleSourceProvider.java deleted file mode 100644 index 82b1a7e2..00000000 --- a/app/src/main/java/org/autojs/autojs/engine/module/UrlModuleSourceProvider.java +++ /dev/null @@ -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 privilegedUris; - private final Iterable 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 privilegedUris, - Iterable 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 privilegedUris, - Iterable 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 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 etags = urlConnection.getHeaderFields().get("ETag"); - if (etags == null || etags.isEmpty()) { - return null; - } - final StringBuilder b = new StringBuilder(); - final Iterator 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; - } - } -} \ No newline at end of file diff --git a/version.properties b/version.properties index 0c9a5fbe..34c3b7fd 100644 --- a/version.properties +++ b/version.properties @@ -1,8 +1,8 @@ -#Fri May 26 20:57:48 CST 2023 -BUILD_TIME=1685105868950 +#Sat Jun 10 13:06:56 CST 2023 +BUILD_TIME=1686373616005 COMPILE_SDK_VERSION=33 JAVA_VERSION=20 MIN_SDK_VERSION=24 TARGET_SDK_VERSION=33 -VERSION_BUILD=1909 +VERSION_BUILD=1910 VERSION_NAME=6.3.1