|
|
@@ -438,6 +438,8 @@ Run: `... -Dtest=LsyOnboardingServiceTest`
|
|
|
| `uploadImage_emptyBytes_rejected` | 传 0 字节 ⇒ `BusinessException(400, ...)` |
|
|
|
| `uploadImage_disallowedExtension_rejected` | `evil.exe` / `x.jpg.php` / `x`(无后缀)⇒ 400。`a.JPG`(大写)⇒ **通过**(扩展名匹配不区分大小写,这条钉住那个 `toLowerCase`) |
|
|
|
| `uploadImage_fileNameWithPathSeparator_rejected` | `../../etc/passwd.jpg` / `a\\b.jpg` ⇒ 400 |
|
|
|
+| `uploadImage_controlCharInFileName_rejected` | `a\r\nX-Evil: 1.jpg` / `a\nb.jpg` / `a\u0000b.jpg` / `a\tb.jpg` ⇒ 400,且**一次上游调用都没发出**(`verifyNoInteractions(client)`)。**★ 钉的是 2026-09-22 实测出的部件头注入**(见实现要点)|
|
|
|
+| `uploadImage_ordinaryFilename_accepted` | **正对照**:`工资表 2026-09.jpg`(中文 + 空格)⇒ **通过**。没有这条,一把过宽的控制字符锁会静默拒掉所有真实上传,而上面那条照样绿 |
|
|
|
| `uploadImage_fileNameLengthCapped` | 超过上游 `img_name` 能接受的长度(文档**没写**上限 ⇒ **不设**上游长度上限),只断言我们**不**擅自截断:长文件名原样送出去。**★ 这条是「不做」的钉子**,防止后来者顺手加个 `substring(0,50)` |
|
|
|
|
|
|
- [ ] **Step 2: 跑测试确认红**
|
|
|
@@ -450,6 +452,18 @@ Run: `... -Dtest=LsyOnboardingServiceTest`
|
|
|
- **不要**把它加进 `validate()`:`validate()` 只管「缺了就静默失败」的项(见它自己的 javadoc),一个带合理默认值的上限不属于那类。`LeshouyingPropertiesTest` 补一条断言它不在必填列表里(**正对照**:确证 `validate()` 在有默认值时不炸)。
|
|
|
- 扩展名白名单:`jpg` / `jpeg` / `png` / `bmp`,取自文档示例出现的形态;**常量 + javadoc**说明依据是「上游字段名全是『照片』语义 + 文档示例是 `.jpg`」,这是我们收窄的,不是上游列举的。
|
|
|
- 文件名含 `/`、`\`、`..` ⇒ 400(**不是**「清洗后照发」:清洗会让日志里的名字与实际发出去的名字不一致,排障时会骗人)。
|
|
|
+- **★ 文件名含 ISO 控制字符 ⇒ 400**(`Character.isISOControl`:CR / LF / tab / NUL / DEL)。
|
|
|
+ 依据是 **2026-09-22 的实测**,不是推测:multipart 部件头是拼在**报文体**里的,
|
|
|
+ `FormHttpMessageConverter` 把 `fileName` 原样写进 `Content-Disposition` ⇒ 名里的 CRLF
|
|
|
+ 会在部件头块里**造出一个新的头行**(探针读数 `filename="a\r\nX-Evil: 1.jpg"`,真 CRLF;
|
|
|
+ 单 LF 同样注入)。实测**三个**头部绑定入口都会注入:`fileName`、`fileFieldName`、`formFields` 的键。
|
|
|
+ - **客户端那层已经替你兜住了**(Task 2 的 fix round 在 `postMultipart` 入口对这三处
|
|
|
+ 抛 `IllegalArgumentException`)—— 但**你仍必须自己校验一次**:客户端抛的是
|
|
|
+ **程序员错误**语义,直接漏出去就是 500;而这里的文件名来自**上传方**,是**用户输入**,
|
|
|
+ 必须变成 `BusinessException(400, 中文名)`。**两层都要,异常类型不同,理由不同。**
|
|
|
+ - **不要**把这条写成「上游要求」—— 上游文档没提这个,这是**我们**的护栏(同 `uploadMaxBytes` 的写法)。
|
|
|
+- **正对照(必须有)**:普通文件名含中文与空格(如 `工资表 2026-09.jpg`)**必须通过**。
|
|
|
+ 只测「坏名字被拒」发现不了一把过宽的锁 —— 那会把真实上传全拒掉。
|
|
|
- 复用 Task 2 的 `postMultipart`:`path = "/api/info/upload/img"`、`fileFieldName = "img_file"`。
|
|
|
- `img_name` 这个**字符串部件**与文件部件上的 filename 都从**同一个形参** `fileName` 派生。
|
|
|
依据:文档把 `img_name` 列为**必填字符串部件**、示例值 `8510_pic_idcard.jpg` 与文件名同形。
|
|
|
@@ -468,6 +482,8 @@ Run: `... -Dtest=LsyOnboardingServiceTest`
|
|
|
| M-4:把路径分隔符改成「清洗后照发」 | `uploadImage_fileNameWithPathSeparator_rejected` **红** |
|
|
|
| M-5:给文件名加 `substring(0, 50)` | `uploadImage_fileNameLengthCapped` **红** |
|
|
|
| M-6(无害):把白名单常量从 `Set.of` 换成 `List.of` | **仍绿** |
|
|
|
+| M-7:去掉控制字符校验 | `uploadImage_controlCharInFileName_rejected` **红** |
|
|
|
+| M-8:把控制字符校验写成「过宽」(连中文/空格也拒) | `uploadImage_ordinaryFilename_accepted` **红**(**★ 这条变异是给锁验宽度的**:只验窄不验宽,过宽的锁照样通过全部拒绝用例)|
|
|
|
|
|
|
- [ ] **Step 6: Commit** —— `feat(进件): 图片上传 - 本地大小与类型护栏在出站之前`
|
|
|
|