fix(est): reject oversized attachments early
This commit is contained in:
@@ -2,7 +2,7 @@ const fs = require("node:fs");
|
||||
const path = require("node:path");
|
||||
const vm = require("node:vm");
|
||||
const base = path.resolve(__dirname, "../../tools/est-test");
|
||||
const context = vm.createContext({ structuredClone, console });
|
||||
const context = vm.createContext({ structuredClone, console, Blob });
|
||||
for (const file of [
|
||||
"native-math.js",
|
||||
"native-formula.js",
|
||||
@@ -19,6 +19,7 @@ for (const file of [
|
||||
"native-resources.js",
|
||||
"native-schedule.js",
|
||||
"native-issues.js",
|
||||
"native-attachments.js",
|
||||
"native-model.js",
|
||||
"native-c3r.js",
|
||||
"native-exchange.js",
|
||||
|
||||
@@ -3,6 +3,21 @@ import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def test_attachment_size_prediction_includes_utf8_and_base64_overhead():
|
||||
result = run(
|
||||
"math",
|
||||
"""(()=>{
|
||||
const p=EstNative.empty();p.name='한글';const file={name:'파일.bin',type:'',size:4};
|
||||
const expected={...p,id:'0'.repeat(24),attachments:[{id:'0'.repeat(36),...file,data:'AAECAw=='}]};
|
||||
return {predicted:EstAttachments.requiredBytes(p,[file]),actual:new Blob([JSON.stringify(expected)]).size,
|
||||
large:EstAttachments.requiredBytes(p,[{name:'large',type:'',size:210000000}]),count:p.attachments.length};
|
||||
})()""",
|
||||
)
|
||||
assert result["predicted"] == result["actual"]
|
||||
assert result["large"] > 256 * 1024 * 1024
|
||||
assert result["count"] == 0
|
||||
|
||||
|
||||
def test_attachment_validation_keeps_valid_data_and_rejects_malformed_content():
|
||||
result = run(
|
||||
"math",
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
## Codex · EST 독립 테스트 앱 (2026-11-04)
|
||||
- [x] 첨부파일 추가 전 256MB 저장 한도 확인 → 대형 파일 차단·기존 작업 보존 시험.
|
||||
- 자체 검증: UTF-8·base64 실제 저장 크기 일치·한도 계산 포함 4개 통과. Edge에서 초과 파일 읽기 전 차단·안내 확인.
|
||||
|
||||
- [x] 전체 수량 반영 시 중복·예약 산출서 코드 차단 → 중복 합산 방지 시험.
|
||||
- 자체 검증: 중복·빈·예약 코드 차단과 기존 수량 보존 포함 시험 19개 통과.
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
저장은 이 폴더의 `data/`에 작업을 보관하고 `.estweb.json` 파일도 내려받습니다. 브라우저 임시 저장은 같은 탭에서 복원하는 용도입니다. JSON은 모든 웹 업무와 원본 압축 파일·바이너리를 보관하는 전체 저장 형식입니다. ESTX 수정 저장은 아래 지원 조건을 따릅니다.
|
||||
|
||||
작업모드의 `첨부파일`에서 파일을 추가·내려받기·삭제할 수 있습니다. 파일은 작업 JSON에 포함되며 이 앱의 저장 요청은 전체 256MB 이하입니다. 첨부파일이 있는 작업은 JSON으로 저장해야 하며, 첨부파일이 빠진 ESTX가 만들어지지 않도록 ESTX 저장을 차단합니다.
|
||||
작업모드의 `첨부파일`에서 파일을 추가·내려받기·삭제할 수 있습니다. 파일은 작업 JSON에 포함되며 이 앱의 저장 요청은 전체 256MB 이하입니다. 제한을 초과하는 파일은 읽기 전에 추가를 차단합니다. 첨부파일이 있는 작업은 JSON으로 저장해야 하며, 첨부파일이 빠진 ESTX가 만들어지지 않도록 ESTX 저장을 차단합니다.
|
||||
|
||||
## 추가 입출력·문서 기능 (2026-11-06)
|
||||
|
||||
|
||||
@@ -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 };
|
||||
})();
|
||||
|
||||
@@ -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 };
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user