# 機能設計書：27-ファイルアップロード

## 1. 概要

### 1.1 機能の目的

本機能は、PayloadCMSにおけるファイルアップロード処理の中核機能を提供する。マルチパートフォームデータの解析、ファイルデータの生成・検証・保存、ファイル配信など、アップロードコレクションに必要な一連の処理を統合的に管理する。

### 1.2 機能の範囲

- マルチパートフォームデータの解析（Busboy使用）
- ファイルメタデータの生成（サイズ、MIME Type、寸法等）
- ファイル名の安全化・重複回避
- ファイルのローカルストレージへの保存
- ファイル配信エンドポイント（Range Request対応）
- 外部URLからのファイル取得
- アップロードフィールドの自動生成

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

| 画面/機能ID | 画面/機能名 | 関連度 |
|------------|------------|-------|
| admin-ui | Admin UI | 高 |
| collection-create | コレクション作成 | 高 |
| collection-update | コレクション更新 | 高 |
| cloud-storage | クラウドストレージ | 高 |

---

## 2. 機能詳細

### 2.1 機能フロー

```
+-------------------+
| クライアント       |
| (multipart/form-data)|
+--------+----------+
         |
         v
+--------+----------+
| processMultipart  |
| (Busboy解析)      |
+--------+----------+
         |
         v
+--------+----------+
| generateFileData  |
| (ファイルデータ生成)|
+--------+----------+
         |
    +----+----+
    |         |
    v         v
+---+---+ +---+---+
|画像処理| |非画像 |
|リサイズ| |ファイル|
+---+---+ +---+---+
    |         |
    +----+----+
         |
         v
+--------+----------+
| uploadFiles       |
| (ファイル保存)    |
+--------+----------+
         |
         v
+--------+----------+
| 配信エンドポイント |
| (Range Request対応)|
+-------------------+
```

### 2.2 入力仕様

#### マルチパートフォームデータ解析オプション（FetchAPIFileUploadOptions）

| パラメータ | 型 | 必須 | デフォルト値 | 説明 |
|-----------|---|------|-------------|------|
| abortOnLimit | boolean | いいえ | false | サイズ制限超過時に中断 |
| createParentPath | boolean | いいえ | false | 親ディレクトリを自動作成 |
| debug | boolean | いいえ | false | デバッグログ出力 |
| defParamCharset | string | いいえ | 'utf8' | パラメータ文字エンコーディング |
| limitHandler | function \| false | いいえ | false | サイズ制限時のカスタムハンドラ |
| parseNested | boolean | いいえ | false | ネストされたフィールドを解析 |
| preserveExtension | boolean | いいえ | false | 拡張子を保持 |
| responseOnLimit | string | いいえ | 'File size limit has been reached' | サイズ制限時のレスポンスメッセージ |
| safeFileNames | boolean | いいえ | false | 安全なファイル名に変換 |
| tempFileDir | string | いいえ | 'tmp' | 一時ファイルディレクトリ |
| uploadTimeout | number | いいえ | 60000 | アップロードタイムアウト（ms） |
| uriDecodeFileNames | boolean | いいえ | false | ファイル名をURIデコード |
| useTempFiles | boolean | いいえ | false | 一時ファイルを使用 |

#### UploadConfig（コレクション設定）

| パラメータ | 型 | 必須 | デフォルト値 | 説明 |
|-----------|---|------|-------------|------|
| adapter | string | いいえ | undefined | ストレージアダプタ名 |
| adminThumbnail | string \| GetAdminThumbnail | いいえ | undefined | サムネイル設定 |
| allowRestrictedFileTypes | boolean | いいえ | false | 制限ファイルタイプを許可 |
| bulkUpload | boolean | いいえ | true | 一括アップロード有効化 |
| cacheTags | boolean | いいえ | true | キャッシュタグを付与 |
| constructorOptions | SharpOptions | いいえ | undefined | Sharpコンストラクタオプション |
| crop | boolean | いいえ | true | 画像クロップ有効化 |
| disableLocalStorage | boolean | いいえ | false | ローカルストレージを無効化 |
| displayPreview | boolean | いいえ | false | プレビュー表示 |
| filenameCompoundIndex | string[] | いいえ | undefined | 複合インデックスフィールド |
| filesRequiredOnCreate | boolean | いいえ | true | 作成時ファイル必須 |
| focalPoint | boolean | いいえ | true | フォーカルポイント有効化 |
| formatOptions | ImageUploadFormatOptions | いいえ | undefined | 画像フォーマットオプション |
| handlers | function[] | いいえ | undefined | カスタムファイルハンドラ |
| imageSizes | ImageSize[] | いいえ | undefined | 画像サイズ定義 |
| mimeTypes | string[] | いいえ | undefined | 許可MIMEタイプ |
| modifyResponseHeaders | function | いいえ | undefined | レスポンスヘッダー修正 |
| pasteURL | object \| false | いいえ | true | URLペースト設定 |
| resizeOptions | ResizeOptions | いいえ | undefined | リサイズオプション |
| staticDir | string | いいえ | collection.slug | 静的ファイルディレクトリ |
| trimOptions | ImageUploadTrimOptions | いいえ | undefined | トリムオプション |
| withMetadata | boolean \| function | いいえ | false | メタデータ付与 |

### 2.3 出力仕様

#### FileData（生成されるファイルデータ）

| フィールド | 型 | 説明 |
|-----------|---|------|
| filename | string | ファイル名 |
| filesize | number | ファイルサイズ（バイト） |
| mimeType | string | MIMEタイプ |
| width | number | 画像幅（画像のみ） |
| height | number | 画像高さ（画像のみ） |
| focalX | number | フォーカルポイントX座標 |
| focalY | number | フォーカルポイントY座標 |
| sizes | FileSizes | 各サイズのファイル情報 |
| url | string | ファイルURL |
| tempFilePath | string | 一時ファイルパス（使用時） |

#### FetchAPIFileUploadResponse

| フィールド | 型 | 説明 |
|-----------|---|------|
| fields | Record<string, string> | フォームフィールド |
| files | Record<string, File> | アップロードファイル |
| error | APIError | エラー（発生時） |

### 2.4 処理詳細

#### 2.4.1 マルチパートフォームデータ解析（processMultipart）

```
処理フロー:
1. Busboyインスタンス生成（headersを渡す）
2. 'field'イベントでフォームフィールドを収集
3. 'file'イベントでファイル処理開始
   - ファイル名解析・サニタイズ
   - MIMEタイプ推論（.glb/.gltfの特殊処理）
   - dataHandler設定（メモリ or 一時ファイル）
   - uploadTimer設定（タイムアウト監視）
   - 'limit'/'data'/'end'/'error'イベント処理
4. 'finish'イベントで完了処理
   - parseNestedオプション時のネスト解析
   - 書き込みPromise待機
5. ReadableStreamからチャンク読み取り
6. 全ファイル完了を待機して結果を返却
```

#### 2.4.2 ファイルデータ生成（generateFileData）

```
処理フロー:
1. uploadEdits解析（crop/focalPoint/resize）
2. ファイル取得
   - 複製時: ローカル/外部ファイル取得
   - shouldReupload判定
3. checkFileRestrictions実行
4. staticPathディレクトリ作成（disableLocalStorage=falseの場合）
5. Sharp処理（リサイズ可能な画像の場合）
   - rotate()でEXIF情報に基づく自動回転
   - resizeOptions適用
   - formatOptions適用
   - trimOptions適用
6. 画像サイズ取得（getImageSize）
7. ファイル名生成
   - sanitize-filenameでサニタイズ
   - getSafeFileName（重複回避）
8. クロップ処理（cropData存在時）
9. createImageSizes呼び出し（imageSizes定義時）
10. FileToSave配列生成
11. newDataにfileData統合
```

#### 2.4.3 ファイル保存（uploadFiles）

```
処理フロー:
1. 全FileToSaveを並列処理
2. 各ファイルでsaveBufferToFile呼び出し
3. fs.writeFileでバッファをファイルに書き込み
4. エラー発生時FileUploadErrorをスロー
```

#### 2.4.4 ファイル配信（getFileHandler）

```
処理フロー:
1. checkFileAccessでアクセス権検証
2. カスタムhandlers実行（設定時）
3. fs.statでファイルメタデータ取得
4. fileTypeFromFileでMIMEタイプ検出
5. parseRangeHeader（Range Request解析）
6. ヘッダー設定
   - Content-Type
   - Accept-Ranges: bytes
   - Content-Security-Policy（SVGの場合）
7. Range Request対応
   - partial: streamFileで範囲読み取り
   - full: streamFileで全体読み取り
8. modifyResponseHeaders適用
9. Response返却
```

---

## 3. データモデル

### 3.1 関連エンティティ

```
+------------------+
| Upload Collection|
+------------------+
| id               |
| filename (unique)|
| mimeType         |
| filesize         |
| width            |
| height           |
| focalX           |
| focalY           |
| url              |
| thumbnailURL     |
| sizes            |
+------------------+
         |
         | 1:N
         v
+------------------+
| sizes.{name}     |
+------------------+
| filename         |
| mimeType         |
| filesize         |
| width            |
| height           |
| url              |
+------------------+
```

### 3.2 自動生成フィールド（getBaseUploadFields）

| フィールド名 | 型 | 説明 | admin設定 |
|-------------|---|------|----------|
| url | text | ファイルURL | hidden, readOnly |
| thumbnailURL | text | サムネイルURL | hidden, readOnly |
| filename | text | ファイル名 | hidden, readOnly, unique |
| mimeType | text | MIMEタイプ | hidden, readOnly |
| filesize | number | ファイルサイズ | hidden, readOnly |
| width | number | 画像幅 | hidden, readOnly |
| height | number | 画像高さ | hidden, readOnly |
| focalX | number | フォーカルX座標 | hidden, disableGroupBy/ListColumn/Filter |
| focalY | number | フォーカルY座標 | hidden, disableGroupBy/ListColumn/Filter |
| sizes | group | 各サイズのファイル情報 | hidden |

---

## 4. ビジネスルール

### 4.1 ファイル名安全化ルール

| ルールID | ルール名 | 条件 | 処理 |
|---------|---------|------|------|
| BR-001 | サニタイズ | - | sanitize-filenameで特殊文字除去 |
| BR-002 | 重複回避 | 既存ファイル存在 | 末尾に-1, -2...を付与 |
| BR-003 | インクリメント | ファイル名-N形式 | Nを+1してインクリメント |
| BR-004 | パストラバーサル防止 | ../または..\\含む | Forbiddenエラー |

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

| ルールID | ルール名 | 条件 | 処理 |
|---------|---------|------|------|
| BR-005 | 制限ファイルタイプ | exe, dll, bat等 | ValidationError |
| BR-006 | allowRestrictedFileTypes | true | 制限チェックをスキップ |
| BR-007 | mimeTypes指定 | 配列指定時 | 指定MIMEタイプのみ許可 |

### 4.3 画像処理ルール

| ルールID | ルール名 | 条件 | 処理 |
|---------|---------|------|------|
| BR-008 | リサイズ可能判定 | jpeg/png/gif/webp/tiff/avif | canResizeImage=true |
| BR-009 | 画像判定 | 上記+svg+jxl | isImage=true |
| BR-010 | アニメーション画像 | gif/webp/avif | animated=true設定 |
| BR-011 | EXIF回転 | 全画像 | sharp.rotate()で自動補正 |
| BR-012 | フォーカルポイント | デフォルト | x:50, y:50（中央） |

### 4.4 アクセス制御ルール

| ルールID | ルール名 | 条件 | 処理 |
|---------|---------|------|------|
| BR-013 | ファイルアクセス | read access | executeAccessで検証 |
| BR-014 | imageSizes検索 | sizes.{name}.filename | OR条件で検索 |

---

## 5. エラー処理

### 5.1 エラー一覧

| エラーコード | エラー名 | 発生条件 | HTTPステータス | メッセージ |
|-------------|---------|---------|---------------|-----------|
| E-001 | FileUploadError | アップロード処理失敗 | 500 | ファイルアップロードエラー |
| E-002 | MissingFile | throwOnMissingFile=true時ファイルなし | 400 | ファイルが見つかりません |
| E-003 | FileRetrievalError | 外部ファイル取得失敗 | 500 | ファイル取得エラー |
| E-004 | ValidationError | ファイル検証失敗 | 400 | 検証エラーメッセージ |
| E-005 | Forbidden | パストラバーサル検出 | 403 | アクセス禁止 |
| E-006 | ErrorDeletingFile | ファイル削除失敗 | 500 | ファイル削除エラー |
| E-007 | APIError | リクエスト不適格 | 500 | Request is not eligible for file upload |
| E-008 | REQUESTED_RANGE_NOT_SATISFIABLE | 無効なRange | 416 | Content-Range: bytes */{size} |

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

```
try {
  // ファイル処理
} catch (err) {
  payload.logger.error(err)
  throw new FileUploadError(req.t)
}
```

---

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

### 6.1 認証・認可

| 項目 | 実装 | 備考 |
|-----|------|------|
| ファイルアクセス制御 | executeAccess | コレクションのread access適用 |
| パストラバーサル防止 | filename検証 | ../および..\\をブロック |

### 6.2 データ保護

| 項目 | 実装 | 備考 |
|-----|------|------|
| SVGセキュリティ | validateSvg | 危険なパターンを検出 |
| SVG配信時CSP | Content-Security-Policy | script-src 'none' |
| 制限ファイルタイプ | RESTRICTED_FILE_EXT_AND_TYPES | exe, dll, bat等をブロック |
| 外部ファイル取得 | Cookie削除 | payload-prefixのCookieを除去 |
| リダイレクト制限 | maxRedirects: 3 | 過剰リダイレクト防止 |

### 6.3 SVG検証パターン

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

---

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

### 7.1 最適化ポイント

| 項目 | 実装 | 効果 |
|-----|------|------|
| 一時ファイル使用 | useTempFiles | 大容量ファイルのメモリ節約 |
| ストリーミング配信 | streamFile | メモリ効率的なファイル配信 |
| 並列保存 | Promise.all | 複数ファイルの並列書き込み |
| Range Request | parseRangeHeader | 部分コンテンツ配信 |

### 7.2 タイムアウト設定

| 項目 | デフォルト値 | 説明 |
|-----|------------|------|
| uploadTimeout | 60000ms | アップロードタイムアウト |
| アップロードタイマー | ファイル単位 | 各ファイルの進捗監視 |

---

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

### 8.1 読み解く順序

1. **型定義の理解** (`types.ts`)
   - File, FileData, FileToSave型
   - UploadConfig, ImageSize型

2. **エントリーポイント** (`fetchAPI-multipart/index.ts`)
   - processMultipartFormdata関数
   - デフォルトオプション設定

3. **マルチパート解析** (`fetchAPI-multipart/processMultipart.ts`)
   - Busboyイベントハンドリング
   - ファイルストリーム処理

4. **ファイルデータ生成** (`generateFileData.ts`)
   - ファイル処理のメインロジック
   - Sharp画像処理の統合

5. **ファイル保存** (`uploadFiles.ts`, `saveBufferToFile.ts`)
   - バッファからファイルへの書き込み

6. **ファイル配信** (`endpoints/getFile.ts`)
   - Range Request処理
   - アクセス制御

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

```
processMultipartFormdata
├── isEligibleRequest
└── processMultipart
    ├── Busboy (npm)
    ├── fileFactory
    ├── memHandler / tempFileHandler
    ├── createUploadTimer
    └── processNested

generateFileData
├── checkFileRestrictions
├── getExternalFile / getFileByPath
├── getSafeFileName
│   ├── docWithFilenameExists
│   └── fileExists
├── getImageSize
├── canResizeImage
├── optionallyAppendMetadata
├── cropImage
└── createImageSizes

uploadFiles
└── saveBufferToFile
    └── fs.writeFile

getFileHandler
├── checkFileAccess
│   └── executeAccess
├── streamFile
├── parseRangeHeader
└── fileTypeFromFile
```

### 8.3 データフロー図

```
Request (multipart/form-data)
        |
        v
+-------+--------+
| Busboy Stream  |
| (チャンク処理)  |
+-------+--------+
        |
        v
+-------+--------+
| File Buffer or |
| Temp File      |
+-------+--------+
        |
        v
+-------+--------+
| generateFileData|
| (メタデータ生成)|
+-------+--------+
        |
    +---+---+
    |       |
    v       v
+---+--+ +--+---+
|Sharp | |Non-  |
|処理  | |Image |
+---+--+ +------+
    |       |
    +---+---+
        |
        v
+-------+--------+
| FileToSave[]   |
| (バッファ+パス) |
+-------+--------+
        |
        v
+-------+--------+
| fs.writeFile   |
| (ディスク保存)  |
+----------------+
```

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

| ファイルパス | 責務 | 行数目安 |
|------------|------|---------|
| packages/payload/src/uploads/types.ts | 型定義 | 1-371 |
| packages/payload/src/uploads/fetchAPI-multipart/index.ts | マルチパートエントリー | 1-75 |
| packages/payload/src/uploads/fetchAPI-multipart/processMultipart.ts | マルチパート解析 | 1-229 |
| packages/payload/src/uploads/generateFileData.ts | ファイルデータ生成 | 1-492 |
| packages/payload/src/uploads/uploadFiles.ts | ファイル保存 | 1-24 |
| packages/payload/src/uploads/saveBufferToFile.ts | バッファ保存 | 1-20 |
| packages/payload/src/uploads/getSafeFilename.ts | ファイル名安全化 | 1-84 |
| packages/payload/src/uploads/checkFileRestrictions.ts | ファイル検証 | 1-173 |
| packages/payload/src/uploads/getBaseFields.ts | フィールド生成 | 1-299 |
| packages/payload/src/uploads/endpoints/getFile.ts | ファイル配信 | 1-165 |
| packages/payload/src/uploads/checkFileAccess.ts | アクセス制御 | 1-65 |
| packages/payload/src/uploads/parseRangeHeader.ts | Range解析 | 1-57 |
| packages/payload/src/uploads/fetchAPI-stream-file/index.ts | ストリーミング | 1-33 |
| packages/payload/src/uploads/validateSvg.ts | SVG検証 | 1-59 |
| packages/payload/src/uploads/getExternalFile.ts | 外部ファイル取得 | 1-103 |
| packages/payload/src/uploads/cropImage.ts | 画像クロップ | 1-96 |
| packages/payload/src/uploads/canResizeImage.ts | リサイズ可否判定 | 1-8 |
| packages/payload/src/uploads/isImage.ts | 画像判定 | 1-14 |
| packages/payload/src/uploads/getImageSize.ts | 画像サイズ取得 | 1-30 |
| packages/payload/src/uploads/deleteAssociatedFiles.ts | 関連ファイル削除 | 1-62 |
| packages/payload/src/uploads/formatFilesize.ts | サイズフォーマット | 1-14 |

---

## 9. 付録

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

| MIME Type | 拡張子 |
|----------|-------|
| application/x-msdownload | exe, dll |
| application/x-executable | exe, com, app, action |
| application/x-msdos-program | bat, cmd |
| application/x-apple-diskimage | dmg |
| application/x-debian-package | deb |
| application/x-redhat-package-manager | rpm |
| application/x-msi | msi |
| application/java-archive | jar, ear, war |
| application/x-ms-shortcut | lnk |
| text/html | htm, html, shtml, xhtml |
| application/x-httpd-php | php, phtml |
| text/javascript | js, jse |
| text/x-python | py |
| text/x-ruby | rb |
| text/x-perl | pl |
| application/x-powershell | ps1, psc1, psd1, psh, psm1 |
| application/x-vbscript | vbe, vbs |

### 9.2 リサイズ可能MIMEタイプ

- image/jpeg
- image/png
- image/gif
- image/webp
- image/tiff
- image/avif

### 9.3 画像判定MIMEタイプ

- image/jpeg
- image/png
- image/gif
- image/svg+xml
- image/webp
- image/avif
- image/jxl
