模块作用域调整,新增__dirname,__filename

This commit is contained in:
aiselp
2023-06-10 13:12:40 +08:00
parent 59c0c73d99
commit f93aa9acfc
6 changed files with 408 additions and 432 deletions

View File

@@ -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<URI>(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 {

View File

@@ -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));
}
}

View File

@@ -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
)
}
}

View File

@@ -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
)
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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