security: 关闭file跨域+混合内容+调试 + SHA256完整性校验

CRITICAL修复:
- 移除allowFileAccessFromFileURLs/allowUniversalAccessFromFileURLs
- 移除MIXED_CONTENT_ALWAYS_ALLOW
- WebContentsDebugging仅DEBUG模式开启
- 下载HTML用Web Crypto SHA256校验(version.txt|url|sha256)
This commit is contained in:
LukeMackin
2026-07-21 20:06:31 +08:00
parent 19860bdec7
commit 7f22d4028f
4 changed files with 31 additions and 22 deletions

View File

@@ -20,26 +20,24 @@ h1{font-size:20px;color:#fff;margin:0}
<h1>Yuzu GCA <span class="badge">v6</span></h1>
<div class="sub">热更新 v6 — 渠道选择器</div>
</header>
<div class="card">
<div class="card-title">更新渠道</div>
<div class="chip-row">
<div id="ch-stable" class="chip chip-on" onclick="selectCh('stable')">Stable</div>
<div id="ch-beta" class="chip chip-off" onclick="selectCh('beta')">Beta</div>
<div id="ch-nightly" class="chip chip-off" onclick="selectCh('nightly')">Nightly</div>
<div class="chip chip-on" onclick="selectCh(this,'stable')">Stable</div>
<div class="chip chip-off" onclick="selectCh(this,'beta')">Beta</div>
<div class="chip chip-off" onclick="selectCh(this,'nightly')">Nightly</div>
</div>
</div>
<button class="btn btn-check" onclick="checkUpdate()">检查更新</button>
<button class="btn btn-download" onclick="applyUpdate()" id="btnApply" disabled>下载更新</button>
<div class="log" id="log"></div>
<script>
var dlUrl='',dlVer='';
var dlUrl='',dlVer='',dlSha='';
var BASE='https://git.childish-ghost.com/LukeMackin/Yuzu-GCA/raw/branch/main/ota';
function selectCh(ch){
function selectCh(el,ch){
document.querySelectorAll('.chip').forEach(function(c){c.className='chip chip-off'});
document.getElementById('ch-'+ch).className='chip chip-on';
el.className='chip chip-on';
log('channel: '+ch)
}
function log(m){document.getElementById('log').innerText+=m+'\n'}
@@ -47,18 +45,23 @@ function checkUpdate(){
fetch(BASE+'/version.txt?t='+Date.now(),{cache:'no-store'})
.then(r=>{if(!r.ok)throw Error('HTTP '+r.status);return r.text()})
.then(function(t){t=t.trim();log('remote: '+t);
var parts=t.indexOf('|')>0?t.split('|'):[t];var remoteVer=parts[0];dlUrl=parts[1]||'';
var parts=t.split('|');var remoteVer=parts[0];dlUrl=parts[1]||'';dlSha=parts[2]||'';
if(!dlUrl)dlUrl=BASE+'/h5/index-v'+remoteVer.replace('0.1.0-dav-android-','')+'.html';
var local='0.1.0-dav-android-6';
if(remoteVer!==local){dlVer=remoteVer;
if(remoteVer!=='0.1.0-dav-android-6'){dlVer=remoteVer;
log('*** NEW VERSION ***');document.getElementById('btnApply').disabled=false}
else log('up to date')})
.catch(function(e){log('ERR: '+e.message)})
}
function applyUpdate(){
fetch(dlUrl+'?t='+Date.now(),{cache:'no-store'})
.then(r=>{if(!r.ok)throw Error('HTTP '+r.status);return r.text()})
.then(function(t){Android.saveCache(t);log('OK! restart app to apply')})
.then(r=>{if(!r.ok)throw Error('HTTP '+r.status);return r.arrayBuffer()})
.then(function(buf){
return crypto.subtle.digest('SHA-256',buf).then(function(hash){
var hex=Array.from(new Uint8Array(hash)).map(function(b){return b.toString(16).padStart(2,'0')}).join('');
if(dlSha&&hex!==dlSha){log('SHA256 MISMATCH!');return}
Android.saveCache(new TextDecoder().decode(buf));log('OK! restart app to apply')
})
})
.catch(function(e){log('ERR: '+e.message)})
}
</script></body></html>

View File

@@ -1 +1 @@
0.1.0-dav-android-6|https://git.childish-ghost.com/LukeMackin/Yuzu-GCA/raw/branch/main/ota/h5/index-v6.html
0.1.0-dav-android-6|https://git.childish-ghost.com/LukeMackin/Yuzu-GCA/raw/branch/main/ota/h5/index-v6.html|79a254cfce0a44a33cc0e56a5beb167d68fc30205d5055c7be6e21f96753dff2

View File

@@ -15,7 +15,7 @@ h1{font-size:18px;color:#38bdf8}
<button class="btn btn-download" onclick="applyUpdate()" id="btnApply" disabled>下载更新</button>
<div class="log" id="log"></div>
<script>
var dlUrl='',dlVer='';
var dlUrl='',dlVer='',dlSha='';
var BASE='https://git.childish-ghost.com/LukeMackin/Yuzu-GCA/raw/branch/main/ota';
function log(m){document.getElementById('log').innerText+=m+'\n'}
@@ -24,7 +24,7 @@ function checkUpdate(){
fetch(BASE+'/version.txt?t='+Date.now(),{cache:'no-store'})
.then(r=>{if(!r.ok)throw Error('HTTP '+r.status);return r.text()})
.then(function(t){t=t.trim();log('remote: '+t);
var parts=t.indexOf('|')>0?t.split('|'):[t];var remoteVer=parts[0];dlUrl=parts[1]||'';
var parts=t.split('|');var remoteVer=parts[0];dlUrl=parts[1]||'';dlSha=parts[2]||'';
if(!dlUrl)dlUrl=BASE+'/h5/index-v'+remoteVer.replace('0.1.0-dav-android-','')+'.html';
var local=document.getElementById('ver').innerText;
if(remoteVer!==local&&remoteVer!=='v'+local&&'v'+remoteVer!==local){
@@ -35,8 +35,15 @@ function checkUpdate(){
function applyUpdate(){
log('downloading: '+dlUrl+' ...');
fetch(dlUrl+'?t='+Date.now(),{cache:'no-store'})
.then(r=>{if(!r.ok)throw Error('HTTP '+r.status);return r.text()})
.then(function(t){Android.saveCache(t);log('OK! restart app to apply')})
.then(r=>{if(!r.ok)throw Error('HTTP '+r.status);return r.arrayBuffer()})
.then(function(buf){
return crypto.subtle.digest('SHA-256',buf).then(function(hash){
var hex=Array.from(new Uint8Array(hash)).map(function(b){return b.toString(16).padStart(2,'0')}).join('');
if(dlSha&&hex!==dlSha){log('SHA256 MISMATCH! expected:'+dlSha.slice(0,16)+'... got:'+hex.slice(0,16)+'...');return}
var text=new TextDecoder().decode(buf);
Android.saveCache(text);log('OK! restart app to apply')
})
})
.catch(function(e){log('ERR: '+e.message)})
}
</script></body></html>

View File

@@ -14,13 +14,12 @@ class MainActivity : AppCompatActivity() {
super.onCreate(savedInstanceState)
val wv = WebView(this).apply {
if (BuildConfig.DEBUG) {
WebView.setWebContentsDebuggingEnabled(true)
}
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")
if (cacheFile.exists()) {