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:
@@ -20,26 +20,24 @@ h1{font-size:20px;color:#fff;margin:0}
|
|||||||
<h1>Yuzu GCA <span class="badge">v6</span></h1>
|
<h1>Yuzu GCA <span class="badge">v6</span></h1>
|
||||||
<div class="sub">热更新 v6 — 渠道选择器</div>
|
<div class="sub">热更新 v6 — 渠道选择器</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-title">更新渠道</div>
|
<div class="card-title">更新渠道</div>
|
||||||
<div class="chip-row">
|
<div class="chip-row">
|
||||||
<div id="ch-stable" class="chip chip-on" onclick="selectCh('stable')">Stable</div>
|
<div class="chip chip-on" onclick="selectCh(this,'stable')">Stable</div>
|
||||||
<div id="ch-beta" class="chip chip-off" onclick="selectCh('beta')">Beta</div>
|
<div class="chip chip-off" onclick="selectCh(this,'beta')">Beta</div>
|
||||||
<div id="ch-nightly" class="chip chip-off" onclick="selectCh('nightly')">Nightly</div>
|
<div class="chip chip-off" onclick="selectCh(this,'nightly')">Nightly</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button class="btn btn-check" onclick="checkUpdate()">检查更新</button>
|
<button class="btn btn-check" onclick="checkUpdate()">检查更新</button>
|
||||||
<button class="btn btn-download" onclick="applyUpdate()" id="btnApply" disabled>下载更新</button>
|
<button class="btn btn-download" onclick="applyUpdate()" id="btnApply" disabled>下载更新</button>
|
||||||
<div class="log" id="log"></div>
|
<div class="log" id="log"></div>
|
||||||
<script>
|
<script>
|
||||||
var dlUrl='',dlVer='';
|
var dlUrl='',dlVer='',dlSha='';
|
||||||
var BASE='https://git.childish-ghost.com/LukeMackin/Yuzu-GCA/raw/branch/main/ota';
|
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.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)
|
log('channel: '+ch)
|
||||||
}
|
}
|
||||||
function log(m){document.getElementById('log').innerText+=m+'\n'}
|
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'})
|
fetch(BASE+'/version.txt?t='+Date.now(),{cache:'no-store'})
|
||||||
.then(r=>{if(!r.ok)throw Error('HTTP '+r.status);return r.text()})
|
.then(r=>{if(!r.ok)throw Error('HTTP '+r.status);return r.text()})
|
||||||
.then(function(t){t=t.trim();log('remote: '+t);
|
.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';
|
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!=='0.1.0-dav-android-6'){dlVer=remoteVer;
|
||||||
if(remoteVer!==local){dlVer=remoteVer;
|
|
||||||
log('*** NEW VERSION ***');document.getElementById('btnApply').disabled=false}
|
log('*** NEW VERSION ***');document.getElementById('btnApply').disabled=false}
|
||||||
else log('up to date')})
|
else log('up to date')})
|
||||||
.catch(function(e){log('ERR: '+e.message)})
|
.catch(function(e){log('ERR: '+e.message)})
|
||||||
}
|
}
|
||||||
function applyUpdate(){
|
function applyUpdate(){
|
||||||
fetch(dlUrl+'?t='+Date.now(),{cache:'no-store'})
|
fetch(dlUrl+'?t='+Date.now(),{cache:'no-store'})
|
||||||
.then(r=>{if(!r.ok)throw Error('HTTP '+r.status);return r.text()})
|
.then(r=>{if(!r.ok)throw Error('HTTP '+r.status);return r.arrayBuffer()})
|
||||||
.then(function(t){Android.saveCache(t);log('OK! restart app to apply')})
|
.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)})
|
.catch(function(e){log('ERR: '+e.message)})
|
||||||
}
|
}
|
||||||
</script></body></html>
|
</script></body></html>
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -15,7 +15,7 @@ h1{font-size:18px;color:#38bdf8}
|
|||||||
<button class="btn btn-download" onclick="applyUpdate()" id="btnApply" disabled>下载更新</button>
|
<button class="btn btn-download" onclick="applyUpdate()" id="btnApply" disabled>下载更新</button>
|
||||||
<div class="log" id="log"></div>
|
<div class="log" id="log"></div>
|
||||||
<script>
|
<script>
|
||||||
var dlUrl='',dlVer='';
|
var dlUrl='',dlVer='',dlSha='';
|
||||||
var BASE='https://git.childish-ghost.com/LukeMackin/Yuzu-GCA/raw/branch/main/ota';
|
var BASE='https://git.childish-ghost.com/LukeMackin/Yuzu-GCA/raw/branch/main/ota';
|
||||||
|
|
||||||
function log(m){document.getElementById('log').innerText+=m+'\n'}
|
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'})
|
fetch(BASE+'/version.txt?t='+Date.now(),{cache:'no-store'})
|
||||||
.then(r=>{if(!r.ok)throw Error('HTTP '+r.status);return r.text()})
|
.then(r=>{if(!r.ok)throw Error('HTTP '+r.status);return r.text()})
|
||||||
.then(function(t){t=t.trim();log('remote: '+t);
|
.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';
|
if(!dlUrl)dlUrl=BASE+'/h5/index-v'+remoteVer.replace('0.1.0-dav-android-','')+'.html';
|
||||||
var local=document.getElementById('ver').innerText;
|
var local=document.getElementById('ver').innerText;
|
||||||
if(remoteVer!==local&&remoteVer!=='v'+local&&'v'+remoteVer!==local){
|
if(remoteVer!==local&&remoteVer!=='v'+local&&'v'+remoteVer!==local){
|
||||||
@@ -35,8 +35,15 @@ function checkUpdate(){
|
|||||||
function applyUpdate(){
|
function applyUpdate(){
|
||||||
log('downloading: '+dlUrl+' ...');
|
log('downloading: '+dlUrl+' ...');
|
||||||
fetch(dlUrl+'?t='+Date.now(),{cache:'no-store'})
|
fetch(dlUrl+'?t='+Date.now(),{cache:'no-store'})
|
||||||
.then(r=>{if(!r.ok)throw Error('HTTP '+r.status);return r.text()})
|
.then(r=>{if(!r.ok)throw Error('HTTP '+r.status);return r.arrayBuffer()})
|
||||||
.then(function(t){Android.saveCache(t);log('OK! restart app to apply')})
|
.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)})
|
.catch(function(e){log('ERR: '+e.message)})
|
||||||
}
|
}
|
||||||
</script></body></html>
|
</script></body></html>
|
||||||
|
|||||||
@@ -14,13 +14,12 @@ class MainActivity : AppCompatActivity() {
|
|||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
val wv = WebView(this).apply {
|
val wv = WebView(this).apply {
|
||||||
WebView.setWebContentsDebuggingEnabled(true)
|
if (BuildConfig.DEBUG) {
|
||||||
|
WebView.setWebContentsDebuggingEnabled(true)
|
||||||
|
}
|
||||||
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")
|
||||||
|
|
||||||
if (cacheFile.exists()) {
|
if (cacheFile.exists()) {
|
||||||
|
|||||||
Reference in New Issue
Block a user