fix(est): reject oversized attachments early

This commit is contained in:
2026-11-07 11:23:39 +09:00
parent d2a8889475
commit 20ef5a6a7f
6 changed files with 45 additions and 5 deletions
+3
View File
@@ -1,4 +1,7 @@
## Codex · EST 독립 테스트 앱 (2026-11-04)
- [x] 첨부파일 추가 전 256MB 저장 한도 확인 → 대형 파일 차단·기존 작업 보존 시험.
- 자체 검증: UTF-8·base64 실제 저장 크기 일치·한도 계산 포함 4개 통과. Edge에서 초과 파일 읽기 전 차단·안내 확인.
- [x] 전체 수량 반영 시 중복·예약 산출서 코드 차단 → 중복 합산 방지 시험.
- 자체 검증: 중복·빈·예약 코드 차단과 기존 수량 보존 포함 시험 19개 통과.
+1 -1
View File
@@ -18,7 +18,7 @@
저장은 이 폴더의 `data/`에 작업을 보관하고 `.estweb.json` 파일도 내려받습니다. 브라우저 임시 저장은 같은 탭에서 복원하는 용도입니다. JSON은 모든 웹 업무와 원본 압축 파일·바이너리를 보관하는 전체 저장 형식입니다. ESTX 수정 저장은 아래 지원 조건을 따릅니다.
작업모드의 `첨부파일`에서 파일을 추가·내려받기·삭제할 수 있습니다. 파일은 작업 JSON에 포함되며 이 앱의 저장 요청은 전체 256MB 이하입니다. 첨부파일이 있는 작업은 JSON으로 저장해야 하며, 첨부파일이 빠진 ESTX가 만들어지지 않도록 ESTX 저장을 차단합니다.
작업모드의 `첨부파일`에서 파일을 추가·내려받기·삭제할 수 있습니다. 파일은 작업 JSON에 포함되며 이 앱의 저장 요청은 전체 256MB 이하입니다. 제한을 초과하는 파일은 읽기 전에 추가를 차단합니다. 첨부파일이 있는 작업은 JSON으로 저장해야 하며, 첨부파일이 빠진 ESTX가 만들어지지 않도록 ESTX 저장을 차단합니다.
## 추가 입출력·문서 기능 (2026-11-06)
+1 -1
View File
@@ -485,7 +485,6 @@ const EstActions = (() => {
EstExchange.install();
EstBusinessActions.install();
EstIssues.install();
EstAttachments.install();
document.addEventListener("click", async (event) => {
try {
const marker = event.target.closest("[data-marker]");
@@ -677,6 +676,7 @@ const EstActions = (() => {
}
}
});
EstAttachments.install();
}
return { install, form };
})();
+23 -2
View File
@@ -20,6 +20,24 @@ const EstAttachments = (() => {
reader.readAsDataURL(file);
});
}
function requiredBytes(project, files) {
const metadata = files.map((file) => ({
id: "0".repeat(36),
name: file.name,
type: file.type || "",
size: file.size,
data: "",
}));
const candidate = {
...project,
id: project.id || "0".repeat(24),
attachments: [...(project.attachments || []), ...metadata],
};
return (
new Blob([JSON.stringify(candidate)]).size +
files.reduce((sum, file) => sum + 4 * Math.ceil(file.size / 3), 0)
);
}
function install() {
document.addEventListener("change", async (event) => {
const input = event.target;
@@ -28,7 +46,10 @@ const EstAttachments = (() => {
key = state.documentKey,
revision = state.revision;
try {
const files = await Promise.all([...input.files].map(read));
const selected = [...input.files];
if (requiredBytes(state.project, selected) > 256 * 1024 * 1024)
throw Error("첨부파일을 포함한 작업이 저장 한도 256MB를 초과합니다.");
const files = await Promise.all(selected.map(read));
if (key !== state.documentKey || revision !== state.revision)
throw Error("작업이 변경되었습니다. 첨부파일을 다시 추가하세요.");
EstApp.change(() => (state.project.attachments ||= []).push(...files), false);
@@ -57,5 +78,5 @@ const EstAttachments = (() => {
}
});
}
return { render, install };
return { render, install, requiredBytes };
})();