# 機能設計書：29-ファイル検証

## 1. 概要

### 1.1 機能の目的

本機能は、PayloadCMSにおけるアップロードファイルの検証処理を提供する。ファイルのMIMEタイプ検証、危険なファイルタイプのブロック、SVG/PDFの内容検証、ファイル存在確認など、アップロード時のセキュリティと整合性を確保する。

### 1.2 機能の範囲

- MIMEタイプのバリデーション
- 制限ファイルタイプ（実行ファイル等）のブロック
- SVGファイルのセキュリティ検証
- PDFファイルの構造検証
- XMLからのSVG検出
- ファイル存在確認（DB/ファイルシステム）
- 拡張子ベースのMIMEタイプフォールバック

### 1.3 関連する画面・機能

| 画面/機能ID | 画面/機能名 | 関連度 |
|------------|------------|-------|
| file-upload | ファイルアップロード | 高 |
| collection-create | コレクション作成 | 高 |
| collection-update | コレクション更新 | 高 |

---

## 2. 機能詳細

### 2.1 機能フロー

```
+-------------------+
| アップロードファイル |
+--------+----------+
         |
         v
+--------+----------+
| checkFileRestrictions|
+--------+----------+
         |
    +----+----+
    |         |
    v         v
+---+---+ +---+----+
|mimeTypes| |制限タイプ|
|指定あり | |チェック |
+---+---+ +---+----+
    |         |
    v         v
+---+---+ +---+----+
|二次検証| |RESTRICTED|
|buffer | |チェック  |
+---+---+ +---+----+
    |         |
    +----+----+
         |
    +----+----+
    |         |
    v         v
+---+---+ +---+----+
|SVG検証| |PDF検証 |
+---+---+ +---+----+
    |         |
    +----+----+
         |
         v
+--------+----------+
| ValidationError   |
| or 通過           |
+-------------------+
```

### 2.2 入力仕様

#### checkFileRestrictions パラメータ

| パラメータ | 型 | 必須 | 説明 |
|-----------|---|------|------|
| collection | CollectionConfig | はい | コレクション設定 |
| file | File | はい | アップロードファイル |
| req | PayloadRequest | はい | リクエストオブジェクト |

#### File オブジェクト

| フィールド | 型 | 説明 |
|-----------|---|------|
| data | Buffer | ファイルデータ |
| mimetype | string | MIMEタイプ |
| name | string | ファイル名 |
| size | number | ファイルサイズ（バイト） |

### 2.3 出力仕様

| 結果 | 説明 |
|-----|------|
| void | 検証通過 |
| ValidationError | 検証失敗（エラーメッセージ付き） |

### 2.4 処理詳細

#### 2.4.1 checkFileRestrictions メイン処理

```
処理フロー:
1. allowRestrictedFileTypes=true の場合、検証スキップ
2. mimeTypes設定時（許可MIMEタイプ指定）:
   a. fileTypeFromBufferでバッファから検出
   b. XMLをSVGとして検出（detectSvgFromXml）
   c. 検出失敗時:
      - useTempFiles=falseなら拡張子フォールバック
      - SVG拡張子ならvalidateSvg実行
      - PDFならvalidatePDF実行
   d. 検出成功時:
      - validateMimeTypeで許可リスト照合
      - PDFならvalidatePDF実行
3. mimeTypes未設定時:
   - RESTRICTED_FILE_EXT_AND_TYPESでチェック
   - 制限タイプに該当すればエラー
4. エラー発生時ValidationErrorをスロー
```

#### 2.4.2 validateMimeType

```typescript
validateMimeType(mimeType: string, allowedMimeTypes: string[]): boolean
```

```
処理ロジック:
1. allowedMimeTypes空配列 → true（全許可）
2. ワイルドカード除去: 'image/*' → 'image/'
3. mimeType.startsWith(cleanedMimeType) で判定
```

#### 2.4.3 validateSvg

```typescript
validateSvg(buffer: Buffer): boolean
```

```
検出パターン:
- <script[\s>] - スクリプトタグ
- \son\w+\s*= - イベントハンドラ
- javascript: - JavaScriptプロトコル
- data:text/html - HTMLデータURL
- <foreignObject[\s>] - 外部オブジェクト
- <iframe[\s>] - iframe
- <object[\s>] - object
- <embed[\s>] - embed
- data:image/svg+xml;base64,*PHNjcmlwdA - Base64エンコードスクリプト
- xlink:href\s*=\s*["']javascript: - XLink JavaScript
- @import - CSSインポート
- <!ENTITY - XMLエンティティ
- <!DOCTYPE[^>]*\[ - 内部サブセット付きDOCTYPE
- <![CDATA[*<script - CDATAスクリプト
```

#### 2.4.4 validatePDF

```typescript
validatePDF(buffer: Buffer): boolean
```

```
検証項目:
1. ヘッダー検証: '%PDF-' で始まるか
2. EOF検証: 末尾1024バイトに '%%EOF' が含まれるか
3. xref検証: 末尾1024バイトに 'xref' が含まれるか
```

#### 2.4.5 detectSvgFromXml

```typescript
detectSvgFromXml(buffer: Buffer): boolean
```

```
処理フロー:
1. バッファサイズ制限（最大2048バイト）
2. XMLエンコーディング検出
   - 安全なエンコーディングのみ許可: ascii, utf-8, utf8
3. XML宣言・コメント・PI除去
4. ルート要素が 'svg' か確認
5. SVG名前空間検証: xmlns="http://www.w3.org/2000/svg"
6. <svg[\s>] タグ存在確認
```

#### 2.4.6 getFileTypeFallback

```typescript
getFileTypeFallback(path: string): { ext: string, mime: string }
```

```
拡張子マッピング:
- css → text/css
- csv → text/csv
- htm/html → text/html
- js → application/javascript
- json → application/json
- md → text/markdown
- svg → image/svg+xml
- xml → application/xml
- yml → application/x-yaml
- その他 → text/plain
```

#### 2.4.7 mimeTypeValidator（フィールドバリデータ）

```typescript
mimeTypeValidator(mimeTypes: string[]): Validate
```

```
処理ロジック:
1. siblingData.filename が無ければ true（ファイルなし）
2. val（MIMEタイプ）が無ければ 'Invalid file type'
3. validateMimeTypeで検証
4. 失敗時 `Invalid file type: '${val}'`
```

#### 2.4.8 docWithFilenameExists

```typescript
docWithFilenameExists({ collectionSlug, filename, prefix?, req }): Promise<boolean>
```

```
処理ロジック:
1. where条件構築: { filename: { equals: filename } }
2. prefix指定時: where.prefix = { equals: prefix }
3. payload.db.findOne で検索
4. ドキュメント存在有無を返却
```

#### 2.4.9 fileExists

```typescript
fileExists(filename: string): Promise<boolean>
```

```
処理ロジック:
1. fs.stat(filename) を実行
2. 成功 → true
3. エラー → false
```

---

## 3. ビジネスルール

### 3.1 MIMEタイプ検証ルール

| ルールID | ルール名 | 条件 | 処理 |
|---------|---------|------|------|
| BR-001 | ワイルドカード対応 | 'image/*' | 'image/'で始まる全MIME許可 |
| BR-002 | 完全一致 | 'image/png' | 'image/png'のみ許可 |
| BR-003 | 空配列 | [] | 全MIME許可 |

### 3.2 制限ファイルタイプルール

| ルールID | ルール名 | 条件 | 処理 |
|---------|---------|------|------|
| BR-004 | 実行ファイル | exe, dll, bat, cmd等 | ブロック |
| BR-005 | スクリプト | js, php, py, rb, pl等 | ブロック |
| BR-006 | パッケージ | jar, deb, rpm, dmg等 | ブロック |
| BR-007 | allowRestrictedFileTypes | true | 制限チェックスキップ |

### 3.3 SVG検証ルール

| ルールID | ルール名 | 条件 | 処理 |
|---------|---------|------|------|
| BR-008 | スクリプト禁止 | <script>タグ | ブロック |
| BR-009 | イベントハンドラ禁止 | onclick等 | ブロック |
| BR-010 | JavaScriptプロトコル禁止 | javascript: | ブロック |
| BR-011 | 外部オブジェクト禁止 | foreignObject, iframe等 | ブロック |

### 3.4 PDF検証ルール

| ルールID | ルール名 | 条件 | 処理 |
|---------|---------|------|------|
| BR-012 | ヘッダー検証 | '%PDF-'なし | 無効PDF |
| BR-013 | EOF検証 | '%%EOF'なし | 無効PDF |
| BR-014 | xref検証 | 'xref'なし | 無効PDF |

### 3.5 XMLからSVG検出ルール

| ルールID | ルール名 | 条件 | 処理 |
|---------|---------|------|------|
| BR-015 | エンコーディング制限 | 非UTF-8/ASCII | 拒否 |
| BR-016 | ルート要素 | <svg>以外 | SVGでない |
| BR-017 | 名前空間必須 | xmlns未設定 | SVGでない |

---

## 4. エラー処理

### 4.1 エラー一覧

| エラーコード | エラー名 | 発生条件 | メッセージ |
|-------------|---------|---------|-----------|
| E-001 | ValidationError | MIMEタイプ不正 | Invalid MIME type: {mime} |
| E-002 | ValidationError | 拡張子不正 | File type {mime} not allowed |
| E-003 | ValidationError | SVG危険 | SVG file contains potentially harmful content |
| E-004 | ValidationError | PDF不正 | Invalid PDF file / Invalid or corrupted PDF file |
| E-005 | ValidationError | 制限タイプ | File type '{mime}' not allowed: Restricted file type detected |

### 4.2 エラー処理フロー

```typescript
if (errors.length > 0) {
  req.payload.logger.error(errors.join(', '))
  throw new ValidationError({
    errors: [{ message: errors.join(', '), path: 'file' }],
  })
}
```

---

## 5. セキュリティ考慮事項

### 5.1 バッファベース検証

| 項目 | 実装 | 備考 |
|-----|------|------|
| 二次検証 | fileTypeFromBuffer | MIMEタイプ偽装対策 |
| 内容検証 | validateSvg, validatePDF | 悪意あるコンテンツ検出 |

### 5.2 SVGセキュリティ

| 項目 | 実装 | 備考 |
|-----|------|------|
| XSS防止 | スクリプトタグ検出 | <script>ブロック |
| イベント防止 | イベントハンドラ検出 | onclick等ブロック |
| インジェクション防止 | 外部オブジェクト検出 | iframe, object等ブロック |

### 5.3 XMLセキュリティ

| 項目 | 実装 | 備考 |
|-----|------|------|
| エンコーディング制限 | UTF-8/ASCIIのみ | エンコーディング攻撃防止 |
| サイズ制限 | 2048バイト | DoS防止 |
| エンティティ制限 | <!ENTITY検出 | XXE防止 |

---

## 6. パフォーマンス考慮事項

### 6.1 最適化ポイント

| 項目 | 実装 | 効果 |
|-----|------|------|
| 早期リターン | allowRestrictedFileTypes | 不要な検証スキップ |
| サイズ制限 | maxSize: 2048 | 大容量ファイルの検証負荷軽減 |
| 末尾検索 | 最後1024バイト | PDF検証の効率化 |

---

## 7. コードリーディングガイド

### 7.1 読み解く順序

1. **型定義の理解** (`types.ts`)
   - File, checkFileRestrictionsParams型
   - FileAllowList型

2. **メイン検証** (`checkFileRestrictions.ts`)
   - checkFileRestrictions関数
   - RESTRICTED_FILE_EXT_AND_TYPES定義

3. **MIMEタイプ検証** (`validateMimeType.ts`, `mimeTypeValidator.ts`)
   - ワイルドカード処理
   - フィールドバリデータ

4. **SVG検証** (`validateSvg.ts`, `detectSvgFromXml.ts`)
   - 危険パターン検出
   - XMLからのSVG検出

5. **PDF検証** (`validatePDF.ts`)
   - ヘッダー/EOF/xref検証

6. **ユーティリティ** (`getFileTypeFallback.ts`, `docWithFilenameExists.ts`, `fileExists.ts`)
   - 拡張子フォールバック
   - ファイル存在確認

### 7.2 プログラム呼び出し階層図

```
checkFileRestrictions
├── fileTypeFromBuffer (npm: file-type)
├── detectSvgFromXml
├── getFileTypeFallback
├── validateMimeType
├── validateSvg
└── validatePDF

mimeTypeValidator
└── validateMimeType

getSafeFileName
├── docWithFilenameExists
│   └── payload.db.findOne
└── fileExists
    └── fs.stat
```

### 7.3 データフロー図

```
Upload File
    |
    v
+---+---+
|Buffer |
+---+---+
    |
    +------------------+
    |                  |
    v                  v
+---+---+         +---+---+
|fileType|        |拡張子  |
|FromBuffer|      |取得   |
+---+---+         +---+---+
    |                  |
    +--------+---------+
             |
             v
    +--------+--------+
    | validateMimeType|
    +--------+--------+
             |
    +--------+--------+
    |        |        |
    v        v        v
+---+--+ +--+--+ +---+---+
|SVG?  | |PDF? | |Other? |
+---+--+ +--+--+ +---+---+
    |        |        |
    v        v        |
+---+--+ +--+--+      |
|validate||validate|   |
|Svg    ||PDF    |    |
+---+--+ +--+--+      |
    |        |        |
    +--------+--------+
             |
             v
    +--------+--------+
    | ValidationError |
    | or Pass         |
    +-----------------+
```

### 7.4 関連ファイル一覧

| ファイルパス | 責務 | 行数目安 |
|------------|------|---------|
| packages/payload/src/uploads/checkFileRestrictions.ts | メイン検証ロジック | 1-173 |
| packages/payload/src/uploads/mimeTypeValidator.ts | フィールドバリデータ | 1-19 |
| packages/payload/src/utilities/validateMimeType.ts | MIMEタイプ検証 | 1-10 |
| packages/payload/src/uploads/validateSvg.ts | SVGセキュリティ検証 | 1-59 |
| packages/payload/src/utilities/validatePDF.ts | PDF構造検証 | 1-18 |
| packages/payload/src/uploads/detectSvgFromXml.ts | XMLからSVG検出 | 1-50 |
| packages/payload/src/uploads/getFileTypeFallback.ts | 拡張子フォールバック | 1-29 |
| packages/payload/src/uploads/docWithFilenameExists.ts | DB存在確認 | 1-35 |
| packages/payload/src/uploads/fileExists.ts | FS存在確認 | 1-12 |

---

## 8. 付録

### 8.1 制限ファイルタイプ一覧

| カテゴリ | MIMEタイプ | 拡張子 |
|---------|----------|-------|
| Windows実行 | application/x-msdownload | exe, dll |
| Unix実行 | application/x-executable | exe, com, app, action |
| DOSプログラム | application/x-msdos-program | bat, cmd |
| macOS | application/x-apple-diskimage | dmg |
| Linux | application/x-debian-package | deb |
| Linux | application/x-redhat-package-manager | rpm |
| Java | application/java-archive | jar, ear, war |
| Webスクリプト | text/html | htm, html, shtml, xhtml |
| PHP | application/x-httpd-php | php, phtml |
| JavaScript | text/javascript | js, jse |
| Python | text/x-python | py |
| Ruby | text/x-ruby | rb |
| Perl | text/x-perl | pl |
| PowerShell | application/x-powershell | ps1, psc1, psd1, psh, psm1 |
| VBScript | application/x-vbscript | vbe, vbs |

### 8.2 拡張子→MIMEタイプマッピング

| 拡張子 | MIMEタイプ |
|-------|----------|
| css | text/css |
| csv | text/csv |
| htm, html | text/html |
| js | application/javascript |
| json | application/json |
| md | text/markdown |
| svg | image/svg+xml |
| xml | application/xml |
| yml | application/x-yaml |
| その他 | text/plain |

### 8.3 SVG危険パターン詳細

| パターン | 説明 | 攻撃手法 |
|---------|------|---------|
| `<script>` | スクリプトタグ | XSS |
| `on\w+=` | イベントハンドラ | XSS |
| `javascript:` | JSプロトコル | XSS |
| `data:text/html` | HTMLデータURL | XSS |
| `<foreignObject>` | 外部HTML埋め込み | XSS |
| `<iframe>` | フレーム埋め込み | クリックジャッキング |
| `xlink:href="javascript:"` | 廃止属性JS | XSS |
| `@import` | CSS外部読み込み | 情報漏洩 |
| `<!ENTITY>` | XMLエンティティ | XXE |
| `<!DOCTYPE [...` | 内部サブセット | XXE |
