chore: 初始提交 - 移除所有硬编码凭据,统一从环境变量读取

This commit is contained in:
yangxiangyuan
2026-06-11 17:10:58 +08:00
commit c223c79b36
1699 changed files with 313940 additions and 0 deletions
+104
View File
@@ -0,0 +1,104 @@
function g(id){return document.getElementById(id)}
function status(t){g('status').textContent=t}
const params = new URLSearchParams(location.search)
const shareId = params.get('id') || location.pathname.split('/').filter(Boolean).pop()
const pAuto = params.get('p')
let meta=null
let fileKey=null
let sdebug=[]
function slog(){ const t=new Date().toISOString(); const msg=[t].concat([].slice.call(arguments)).join(' '); sdebug.push(msg); if(sdebug.length>200) sdebug.shift(); const el=g('shareDebug'); if(el) el.textContent=sdebug.join('\n') }
const CLIENT_VER = 'sharejs-2025-11-25-02'
slog('client.ver', CLIENT_VER)
slog('share.id', shareId)
slog('location', location.href)
async function deriveShareKey(pwd, salt){
const material = await crypto.subtle.importKey('raw', new TextEncoder().encode(pwd), 'PBKDF2', false, ['deriveKey'])
return crypto.subtle.deriveKey({ name:'PBKDF2', hash:'SHA-256', salt, iterations:200000 }, material, { name:'AES-GCM', length:256 }, false, ['encrypt','decrypt'])
}
async function unwrapFileKey(wrappedB64, shareKey, ivB64){
const iv = new Uint8Array([...atob(ivB64)].map(c=>c.charCodeAt(0)))
const ct = new Uint8Array([...atob(wrappedB64)].map(c=>c.charCodeAt(0)))
const pt = await crypto.subtle.decrypt({ name:'AES-GCM', iv }, shareKey, ct)
return new Uint8Array(pt)
}
async function deriveFileKeyFromBytes(bytes){
return crypto.subtle.importKey('raw', bytes, { name:'AES-GCM', length:256 }, false, ['decrypt'])
}
async function ivForChunk(saltBytes, index){
const data = new Uint8Array(saltBytes.length+4)
data.set(saltBytes,0)
data.set(new Uint8Array([index>>>24,(index>>>16)&255,(index>>>8)&255,index&255]), saltBytes.length)
const d = await crypto.subtle.digest('SHA-256', data)
return new Uint8Array(d).slice(0,12)
}
async function openShare(){
const pwd = g('sharePwd').value
slog('open.pwd', pwd)
if(!pwd){status('请输入分享密码');return}
status('验证中...')
const started = performance.now()
const r = await fetch(`/api/psc/share/${shareId}/init`, { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({ password: pwd }) })
let j=null
try { j = await r.json() } catch(e){ j={ error:'bad_json' } }
slog('open.status', r.status); slog('open.json', JSON.stringify(j)); if(j && j.error) slog('open.json.error', j.error)
if(j && j.expected) slog('open.hash.expected', j.expected)
if(j && j.actual) slog('open.hash.actual', j.actual)
if(j && j.shareSaltB64) slog('open.shareSaltB64.len', (j.shareSaltB64||'').length)
slog('open.ms', (performance.now()-started).toFixed(2))
if(!r.ok){ status('密码错误或分享不存在'); return }
meta = j
g('fname').textContent = j.name
g('fsize').textContent = (j.size/1048576).toFixed(2)+' MB'
const shareSalt = new Uint8Array([...atob(j.shareSaltB64)].map(c=>c.charCodeAt(0)))
const sKey = await deriveShareKey(pwd, shareSalt)
const fkBytes = await unwrapFileKey(j.wrappedKeyB64, sKey, j.ivB64)
fileKey = await deriveFileKeyFromBytes(fkBytes)
const fkHash = new Uint8Array(await crypto.subtle.digest('SHA-256', fkBytes))
slog('fk.hash', btoa(String.fromCharCode(...fkHash)))
slog('meta.fileId', j.fileId)
slog('meta.chunkCount', j.chunkCount)
slog('unwrap.len', fkBytes.length)
g('info').classList.remove('hidden')
status('验证通过')
}
async function download(){
if(!meta || !fileKey){ await openShare(); if(!meta || !fileKey){ status('请先验证分享'); return } }
status('开始下载')
const fileSalt = new Uint8Array([...atob(meta.fileSalt)].map(c=>c.charCodeAt(0)))
const chunks = new Array(meta.chunkCount)
let active=0, next=0, processed=0
const maxParallel=4
async function runOne(i){
try {
active++
slog('dl.req', i)
const r = await fetch(`/api/psc/share/${shareId}/chunk?index=${i}`)
slog('dl.http', r.status)
if(!r.ok){ throw new Error('http '+r.status) }
const buf = new Uint8Array(await r.arrayBuffer())
slog('dl.buf.len', buf.length)
const iv = await ivForChunk(fileSalt,i)
slog('dl.iv.b64', btoa(String.fromCharCode(...iv)))
const pt = new Uint8Array(await crypto.subtle.decrypt({ name:'AES-GCM', iv }, fileKey, buf))
chunks[i]=pt; processed+=pt.length; status(`已下载 ${(processed/meta.size*100).toFixed(2)}%`)
slog('dl.ok', i, pt.length)
} catch(e){ slog('dl.err', i, e && (e.message||String(e))); status('分片下载失败') }
finally { active--; schedule() }
}
function schedule(){
slog('dl.schedule', 'active', active, 'next', next)
while(active<maxParallel && next<meta.chunkCount){ runOne(next); next++ }
if(active===0 && next>=meta.chunkCount){ finish() }
}
function finish(){
const out=new Uint8Array(meta.size); let off=0; for(let i=0;i<meta.chunkCount;i++){ out.set(chunks[i],off); off+=chunks[i].length }
slog('dl.finish.size', out.length)
const blob=new Blob([out]); const a=document.createElement('a'); a.href=URL.createObjectURL(blob); a.download=meta.name; a.target='_blank'; a.click(); setTimeout(()=>URL.revokeObjectURL(a.href), 30000); status('下载完成')
}
schedule()
}
g('btnOpen').onclick=openShare
g('btnDownload').onclick=download
if(pAuto){ g('sharePwd').value = pAuto; slog('auto.p', pAuto); openShare() }
const btnCopy = document.getElementById('btnCopyShareDebug'); if(btnCopy){ btnCopy.onclick = async()=>{ try{ await navigator.clipboard.writeText((g('shareDebug')?.textContent)||''); status('调试信息已复制') } catch{ status('请手动复制调试信息') } } }
document.addEventListener('visibilitychange', ()=>{ if(document.visibilityState==='visible'){ const inputs = Array.from(document.querySelectorAll('input,textarea')); inputs.forEach(el=>{ if(el.type==='checkbox' || el.type==='radio'){ el.checked=false } else { el.value='' } }) } })