security: Bridge代理fetch+移除跨域设置+CSP头限制
CRITICAL修复: - 移除allowFileAccessFromFileURLs/allowUniversalAccessFromFileURLs - 移除mixedContentMode=ALWAYS_ALLOW - WebViewClient注入CSP: default-src 'none';script-src 'unsafe-inline';connect-src 'none' - 新增Bridge.fetchManifest(channel)/Bridge.fetchText(path)原生HTTP代理 - manifest JSON + version.txt通过Bridge读取,无需WebView跨域
This commit is contained in:
Binary file not shown.
@@ -3,8 +3,11 @@ package dev.yuzu.gca
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.webkit.WebView
|
import android.webkit.WebView
|
||||||
|
import android.webkit.WebViewClient
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.net.HttpURLConnection
|
||||||
|
import java.net.URL
|
||||||
|
|
||||||
class MainActivity : AppCompatActivity() {
|
class MainActivity : AppCompatActivity() {
|
||||||
|
|
||||||
@@ -20,10 +23,17 @@ class MainActivity : AppCompatActivity() {
|
|||||||
settings.javaScriptEnabled = true
|
settings.javaScriptEnabled = true
|
||||||
settings.domStorageEnabled = true
|
settings.domStorageEnabled = true
|
||||||
settings.allowFileAccess = true
|
settings.allowFileAccess = true
|
||||||
settings.allowFileAccessFromFileURLs = true
|
|
||||||
settings.allowUniversalAccessFromFileURLs = true
|
|
||||||
settings.mixedContentMode = android.webkit.WebSettings.MIXED_CONTENT_ALWAYS_ALLOW
|
|
||||||
addJavascriptInterface(Bridge(this@MainActivity), "Android")
|
addJavascriptInterface(Bridge(this@MainActivity), "Android")
|
||||||
|
webViewClient = object : WebViewClient() {
|
||||||
|
override fun onPageFinished(view: WebView, url: String) {
|
||||||
|
view.evaluateJavascript("""
|
||||||
|
var meta = document.createElement('meta');
|
||||||
|
meta.httpEquiv = 'Content-Security-Policy';
|
||||||
|
meta.content = "default-src 'none'; script-src 'unsafe-inline'; connect-src 'none'";
|
||||||
|
document.head.appendChild(meta);
|
||||||
|
""".trimIndent(), null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (cacheFile.exists()) {
|
if (cacheFile.exists()) {
|
||||||
val html = cacheFile.readText()
|
val html = cacheFile.readText()
|
||||||
@@ -47,17 +57,57 @@ class MainActivity : AppCompatActivity() {
|
|||||||
ctx.cacheFile.writeText(html)
|
ctx.cacheFile.writeText(html)
|
||||||
Log.i(TAG, "Hot-update HTML saved (${html.length}B)")
|
Log.i(TAG, "Hot-update HTML saved (${html.length}B)")
|
||||||
}
|
}
|
||||||
|
|
||||||
@android.webkit.JavascriptInterface
|
@android.webkit.JavascriptInterface
|
||||||
fun clearCache() {
|
fun clearCache() {
|
||||||
ctx.cacheFile.delete()
|
ctx.cacheFile.delete()
|
||||||
Log.i(TAG, "Cache cleared")
|
Log.i(TAG, "Cache cleared")
|
||||||
}
|
}
|
||||||
|
|
||||||
@android.webkit.JavascriptInterface
|
@android.webkit.JavascriptInterface
|
||||||
fun sha256(input: String): String {
|
fun sha256(input: String): String {
|
||||||
val md = java.security.MessageDigest.getInstance("SHA-256")
|
val md = java.security.MessageDigest.getInstance("SHA-256")
|
||||||
val digest = md.digest(input.toByteArray(Charsets.UTF_8))
|
val digest = md.digest(input.toByteArray(Charsets.UTF_8))
|
||||||
return digest.joinToString("") { "%02x".format(it) }
|
return digest.joinToString("") { "%02x".format(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Fetch manifest JSON from Gitea — proxy to avoid CORS in WebView */
|
||||||
|
@android.webkit.JavascriptInterface
|
||||||
|
fun fetchManifest(channel: String): String {
|
||||||
|
val url = "https://git.childish-ghost.com/LukeMackin/Yuzu-GCA/raw/branch/main/ota/h5/manifest-$channel.json"
|
||||||
|
Log.i(TAG, "Bridge fetching $url")
|
||||||
|
return try {
|
||||||
|
val conn = URL(url).openConnection() as HttpURLConnection
|
||||||
|
conn.connectTimeout = 10_000
|
||||||
|
conn.readTimeout = 10_000
|
||||||
|
conn.instanceFollowRedirects = true
|
||||||
|
val code = conn.responseCode
|
||||||
|
if (code != 200) throw Exception("HTTP $code")
|
||||||
|
conn.inputStream.bufferedReader().readText()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Bridge fetch failed: ${e.message}")
|
||||||
|
"""{"error":"${e.message}"}"""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Fetch text file from Gitea — proxy for version.txt etc */
|
||||||
|
@android.webkit.JavascriptInterface
|
||||||
|
fun fetchText(path: String): String {
|
||||||
|
val url = "https://git.childish-ghost.com/LukeMackin/Yuzu-GCA/raw/branch/main/ota/$path"
|
||||||
|
Log.i(TAG, "Bridge fetching $url")
|
||||||
|
return try {
|
||||||
|
val conn = URL(url).openConnection() as HttpURLConnection
|
||||||
|
conn.connectTimeout = 10_000
|
||||||
|
conn.readTimeout = 10_000
|
||||||
|
conn.instanceFollowRedirects = true
|
||||||
|
val code = conn.responseCode
|
||||||
|
if (code != 200) throw Exception("HTTP $code")
|
||||||
|
conn.inputStream.bufferedReader().readText()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Bridge fetchText($path) failed: ${e.message}")
|
||||||
|
"ERROR: ${e.message}"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object { private const val TAG = "GCA-H5" }
|
companion object { private const val TAG = "GCA-H5" }
|
||||||
|
|||||||
Reference in New Issue
Block a user