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:
LukeMackin
2026-07-21 23:02:00 +08:00
parent db8cfdf6e3
commit b88311a2d0
2 changed files with 53 additions and 3 deletions

View File

@@ -3,8 +3,11 @@ package dev.yuzu.gca
import android.os.Bundle
import android.util.Log
import android.webkit.WebView
import android.webkit.WebViewClient
import androidx.appcompat.app.AppCompatActivity
import java.io.File
import java.net.HttpURLConnection
import java.net.URL
class MainActivity : AppCompatActivity() {
@@ -20,10 +23,17 @@ class MainActivity : AppCompatActivity() {
settings.javaScriptEnabled = true
settings.domStorageEnabled = true
settings.allowFileAccess = true
settings.allowFileAccessFromFileURLs = true
settings.allowUniversalAccessFromFileURLs = true
settings.mixedContentMode = android.webkit.WebSettings.MIXED_CONTENT_ALWAYS_ALLOW
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()) {
val html = cacheFile.readText()
@@ -47,17 +57,57 @@ class MainActivity : AppCompatActivity() {
ctx.cacheFile.writeText(html)
Log.i(TAG, "Hot-update HTML saved (${html.length}B)")
}
@android.webkit.JavascriptInterface
fun clearCache() {
ctx.cacheFile.delete()
Log.i(TAG, "Cache cleared")
}
@android.webkit.JavascriptInterface
fun sha256(input: String): String {
val md = java.security.MessageDigest.getInstance("SHA-256")
val digest = md.digest(input.toByteArray(Charsets.UTF_8))
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" }