Harden server and fix client-side edge cases

Python: per-file error handling in cleanup, single stat() in download,
S_ISREG check, Content-Disposition escaping, Content-Length on all
responses, route prefix passed to handlers (no magic numbers), validate
empty upload, ensure_cert handles partial cert pair, narrower exception
catches.

JS: visibility-aware polling, concurrent upload guard, fetch r.ok checks,
missing .catch() on delete/loadFiles, REFRESH_SEC→REFRESH_MS rename,
explicit parseInt radix, transition: all→specific properties.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-10 20:47:47 +04:00
co-authored by Claude Opus 4.6
parent ebe5c6ec90
commit 082a80a6c5
2 changed files with 66 additions and 38 deletions
+23 -14
View File
@@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="ru">
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
@@ -34,7 +34,7 @@ h1 { font-size: 1.4rem; margin-bottom: 16px; color: #fff }
width: 100%; max-width: 600px;
border: 2px dashed var(--text-empty); border-radius: 12px;
padding: 48px 24px; text-align: center;
cursor: pointer; transition: all .2s;
cursor: pointer; transition: border-color .2s, background .2s;
margin-bottom: 20px; position: relative;
}
#drop.over { border-color: var(--accent); background: rgba(79,195,247,.08) }
@@ -192,7 +192,7 @@ const drop = $('drop'), fi = $('fileinput'), flist = $('flist'),
const _cfg = typeof CFG !== 'undefined' ? CFG : {};
const defaultLang = _cfg.lang || 'ru';
const REFRESH_SEC = (_cfg.refresh || 5) * 1000;
const REFRESH_MS = (_cfg.refresh || 5) * 1000;
const PER_PAGE_OPTS = _cfg.perPage || [10, 25, 50, 100];
/* ── i18n ── */
@@ -327,7 +327,7 @@ function copyFile(name, type) {
const url = '/dl/' + encodeURIComponent(name);
if (type === 'text') {
fetch(url).then(r => r.text()).then(text => copyText(text))
fetch(url).then(r => { if (!r.ok) throw 0; return r.text() }).then(text => copyText(text))
.catch(() => toast(t().copyFail));
} else if (type === 'image') {
@@ -336,7 +336,7 @@ function copyFile(name, type) {
copyText(link, t().linkCopied);
return;
}
const pngBlob = fetch(url).then(r => r.blob()).then(toPngBlob);
const pngBlob = fetch(url).then(r => { if (!r.ok) throw 0; return r.blob() }).then(toPngBlob);
navigator.clipboard.write([new ClipboardItem({ 'image/png': pngBlob })])
.then(() => toast(t().imgCopied))
.catch(() => copyText(link, t().linkCopied));
@@ -348,8 +348,10 @@ function copyFile(name, type) {
/* ── Upload ── */
let uploading = false;
function upload(fileList) {
if (!fileList.length) return;
if (!fileList.length || uploading) return;
uploading = true;
const fd = new FormData();
for (const f of fileList) fd.append('files', f);
@@ -364,8 +366,10 @@ function upload(fileList) {
ptext.textContent = fmtSize(e.loaded) + ' / ' + fmtSize(e.total) + ' (' + pct + '%)';
};
const done = () => { uploading = false; drop.classList.remove('uploading') };
xhr.onload = () => {
drop.classList.remove('uploading');
done();
pfill.style.width = '100%';
setTimeout(() => { $('progress').style.display = 'none'; pfill.style.width = '0' }, 1200);
if (xhr.status === 200) {
@@ -375,14 +379,14 @@ function upload(fileList) {
} else { toast(t().uploadFail) }
};
xhr.onerror = () => { drop.classList.remove('uploading'); toast(t().uploadError) };
xhr.onerror = () => { done(); toast(t().uploadError) };
xhr.open('POST', '/upload');
xhr.send(fd);
}
/* ── Pagination ── */
let perPage = parseInt(localStorage.getItem('share-perpage')) || PER_PAGE_OPTS[0];
let perPage = parseInt(localStorage.getItem('share-perpage'), 10) || PER_PAGE_OPTS[0];
let curPage = 1;
let allFiles = [];
@@ -462,7 +466,7 @@ function loadFiles() {
fetch('/files').then(r => r.json()).then(files => {
allFiles = files;
renderFiles();
});
}).catch(() => {});
}
/* ── Event delegation ── */
@@ -475,20 +479,21 @@ flist.addEventListener('click', e => {
if (action === 'copy') copyFile(name, el.dataset.type);
else if (action === 'del') {
fetch('/del/' + encodeURIComponent(name), { method: 'DELETE' })
.then(() => { toast(t().deleted); loadFiles() });
.then(() => { toast(t().deleted); loadFiles() })
.catch(() => {});
}
});
$('pager').addEventListener('click', e => {
const el = e.target.closest('[data-action="page"]');
if (!el || el.disabled) return;
setPage(parseInt(el.dataset.page));
setPage(parseInt(el.dataset.page, 10));
});
$('per-page').addEventListener('click', e => {
const el = e.target.closest('[data-action="pp"]');
if (!el) return;
setPerPage(parseInt(el.dataset.pp));
setPerPage(parseInt(el.dataset.pp, 10));
});
/* ── Events ── */
@@ -526,7 +531,11 @@ document.addEventListener('paste', e => {
});
setLang(lang);
setInterval(loadFiles, REFRESH_SEC);
let pollId = setInterval(loadFiles, REFRESH_MS);
document.addEventListener('visibilitychange', () => {
if (document.hidden) { clearInterval(pollId); }
else { loadFiles(); pollId = setInterval(loadFiles, REFRESH_MS); }
});
</script>
</body>
</html>