# 機能設計書 67-escapeHtml()

## 概要

本ドキュメントは、Vue 3の`@vue/shared`パッケージが提供する`escapeHtml()`関数の設計仕様を記述する。この関数はHTML特殊文字をHTMLエンティティにエスケープし、XSS（クロスサイトスクリプティング）攻撃を防止するセキュリティ基盤関数である。

### 本機能の処理概要

`escapeHtml()`は、入力文字列中のHTML特殊文字（`"`, `&`, `'`, `<`, `>`）をHTMLエンティティ（`&quot;`, `&amp;`, `&#39;`, `&lt;`, `&gt;`）に変換する関数である。SSRレンダラーでのHTML出力、属性値のエスケープ、テキスト補間のエスケープ等、Vue 3フレームワーク全体のXSS対策の根幹を成す。また、`escapeHtmlComment()`はHTMLコメント内の危険なパターンを除去する関数、`getEscapedCssVarName()`はCSS変数名の特殊文字をエスケープする関数である。

**業務上の目的・背景**：ウェブアプリケーションにおいて、ユーザー入力値がそのままHTMLに挿入されると、悪意のあるスクリプトが実行されるXSS攻撃のリスクがある。Vue 3はテンプレートの動的コンテンツを自動的にエスケープすることで、デフォルトでXSS対策を提供する。`escapeHtml()`はこの自動エスケープの実装基盤である。

**機能の利用シーン**：
- SSRレンダリングでのテキストノード出力
- SSRレンダリングでの属性値出力
- テキスト補間（`{{ }}`）のエスケープ処理
- SSRヘルパー関数（ssrInterpolate、ssrRenderAttrs等）内部

**主要な処理内容**：
1. 正規表現`/["'&<>]/`で特殊文字の存在をチェック（高速な早期リターン最適化）
2. マッチした位置から文字列を走査し、charCodeAtで特殊文字を判定
3. 各特殊文字を対応するHTMLエンティティに置換
4. 非特殊文字部分はslice()でまとめてコピーし、文字列連結のオーバーヘッドを最小化

**関連システム・外部連携**：`@vue/server-renderer`（ssrRenderAttrs、ssrInterpolate等）、`@vue/runtime-dom`（innerHTML設定時）。

**権限による制御**：特になし。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | - | - | フレームワーク内部セキュリティ関数。直接的な画面関連なし |

## 機能種別

セキュリティユーティリティ（XSSエスケープ処理）

## 入力仕様

### 入力パラメータ

#### escapeHtml()

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| string | `unknown` | Yes | エスケープ対象の値。文字列に変換される | `'' + string`で文字列変換 |

#### escapeHtmlComment()

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| src | `string` | Yes | HTMLコメント内容 | - |

#### getEscapedCssVarName()

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| key | `string` | Yes | CSS変数名 | - |
| doubleEscape | `boolean` | Yes | ダブルエスケープの有無 | - |

### 入力データソース

Vue 3の各パッケージ（特にSSRレンダラー）から呼び出される。

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| escapeHtml戻り値 | `string` | HTMLエンティティに変換された安全な文字列 |
| escapeHtmlComment戻り値 | `string` | 危険パターンが除去されたコメント文字列 |
| getEscapedCssVarName戻り値 | `string` | 特殊文字がエスケープされたCSS変数名 |

### 出力先

呼び出し元への戻り値。

## 処理フロー

### 処理シーケンス

#### escapeHtml()
```
1. 入力値を文字列に変換（'' + string）
2. 正規表現 /["'&<>]/ で特殊文字の有無を事前チェック
   └─ マッチなし → 元の文字列をそのまま返却（早期リターン）
3. マッチ位置から文字列末尾まで1文字ずつ走査
   ├─ charCode 34 (") → '&quot;'
   ├─ charCode 38 (&) → '&amp;'
   ├─ charCode 39 (') → '&#39;'
   ├─ charCode 60 (<) → '&lt;'
   ├─ charCode 62 (>) → '&gt;'
   └─ その他 → continue（スキップ）
4. 非特殊文字の連続部分をslice()でまとめて追加
5. エスケープ済み文字列を返却
```

### フローチャート

```mermaid
flowchart TD
    A[escapeHtml呼び出し] --> B['' + string で文字列変換]
    B --> C{正規表現で特殊文字チェック}
    C -->|なし| D[元の文字列をそのまま返却]
    C -->|あり| E[マッチ位置から走査開始]
    E --> F{charCodeAt判定}
    F -->|34| G["'&quot;'"]
    F -->|38| H["'&amp;'"]
    F -->|39| I["'&#39;'"]
    F -->|60| J["'&lt;'"]
    F -->|62| K["'&gt;'"]
    F -->|その他| L[continue]
    G --> M[lastIndex更新、html += escaped]
    H --> M
    I --> M
    J --> M
    K --> M
    L --> E
    M --> E
    E -->|走査完了| N[残りのsliceを追加して返却]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-67-01 | 5文字エスケープ | `"`, `&`, `'`, `<`, `>` の5種類の文字をHTMLエンティティに変換する | 常時 |
| BR-67-02 | 早期リターン最適化 | 特殊文字を含まない文字列は正規表現チェックのみで元の文字列を返却する | 特殊文字なし |
| BR-67-03 | 暗黙の型変換 | 入力が文字列でない場合、`'' + string`で文字列に変換する | 非文字列入力時 |
| BR-67-04 | コメントエスケープ | escapeHtmlComment()は`-->`、`<!--`、`--!>`、`<!-`等のパターンを除去する | HTMLコメント内容のエスケープ時 |
| BR-67-05 | CSS変数名エスケープ | getEscapedCssVarName()はスペース、記号、括弧等の特殊文字をバックスラッシュでエスケープする | CSS変数名エスケープ時 |

### 計算ロジック

エスケープ対応表:

| 文字 | charCode | エンティティ |
|-----|----------|------------|
| `"` | 34 | `&quot;` |
| `&` | 38 | `&amp;` |
| `'` | 39 | `&#39;` |
| `<` | 60 | `&lt;` |
| `>` | 62 | `&gt;` |

## データベース操作仕様

該当なし。

## エラー処理

エラーは発生しない。全ての入力に対して安全な文字列を返す。

### リトライ仕様

該当なし。

## トランザクション仕様

該当なし。

## パフォーマンス要件

- 正規表現による事前チェックで、特殊文字を含まない文字列の処理を高速化
- charCodeAtによるswitch文での文字判定は、文字列比較よりも高速
- lastIndex追跡により、非特殊文字の連続部分をslice()でまとめてコピーし、文字列連結回数を最小化

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

- 本関数はVue 3のXSS対策の根幹となるセキュリティ関数
- `"`, `&`, `'`, `<`, `>` の5文字を網羅的にエスケープすることで、HTML属性値およびテキストコンテンツでの悪意のあるスクリプト挿入を防止
- `escapeHtmlComment()`はHTMLコメントを利用した攻撃パターン（`-->`で早期閉じ等）を防止

## 備考

- `escapeRE = /["'&<>]/`は最初のマッチ位置を特定するためだけに使用される。実際のエスケープ処理はcharCodeAtによるswitch文で行われる
- この実装パターンは、正規表現のreplace()よりもパフォーマンスが良いことが実証されているアプローチ
- `cssVarNameEscapeSymbolsRE`はCSS変数名に使用できない特殊文字（スペース、`!`, `"`, `#`, `$`, `%`, `&`, `(`, `)`, `*`, `+`, `,`, `.`, `/`, `:`, `;`, `<`, `=`, `>`, `?`, `@`, `[`, `\`, `]`, `^`, `` ` ``, `{`, `|`, `}`, `~`）を網羅する

---

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

本機能を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: escapeHtml()を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | escapeHtml.ts | `packages/shared/src/escapeHtml.ts` | escapeRE正規表現（1行目）: /["'&<>]/ |
| 1-2 | escapeHtml.ts | `packages/shared/src/escapeHtml.ts` | escapeHtml()関数（3-45行目） |

**主要処理フロー**:
1. **4行目**: `const str = '' + string` で文字列変換
2. **5行目**: `const match = escapeRE.exec(str)` で特殊文字の最初の出現位置を検索
3. **7-8行目**: マッチなしなら元の文字列を返却（早期リターン最適化）
4. **15行目**: `for (index = match.index; ...)` でマッチ位置から走査開始
5. **16-33行目**: `str.charCodeAt(index)`のswitch文で5種類の特殊文字を判定
6. **36-38行目**: `html += str.slice(lastIndex, index)` で非特殊文字部分をまとめてコピー
7. **41行目**: `html += escaped` でエスケープ済み文字を追加
8. **44行目**: 残りの部分をsliceして返却

**読解のコツ**: `lastIndex`変数が、前回エスケープした文字の次の位置を追跡している。非特殊文字の連続部分はslice()で一括コピーすることで、文字列連結の回数を最小化している。

#### Step 2: escapeHtmlComment()を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | escapeHtml.ts | `packages/shared/src/escapeHtml.ts` | commentStripRE正規表現（48行目）: `/^-?>|<!--|-->|--!>|<!-$/g` |
| 2-2 | escapeHtml.ts | `packages/shared/src/escapeHtml.ts` | escapeHtmlComment()関数（50-52行目）: 危険パターンを空文字列に置換 |

#### Step 3: getEscapedCssVarName()を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | escapeHtml.ts | `packages/shared/src/escapeHtml.ts` | cssVarNameEscapeSymbolsRE正規表現（54-55行目） |
| 3-2 | escapeHtml.ts | `packages/shared/src/escapeHtml.ts` | getEscapedCssVarName()関数（57-64行目）: doubleEscapeフラグで二重エスケープ対応 |

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

```
escapeHtml(string)
    │
    ├─ '' + string        [型変換]
    ├─ escapeRE.exec(str) [事前チェック]
    └─ charCodeAt()       [文字判定+エスケープ]

escapeHtmlComment(src)
    │
    └─ src.replace(commentStripRE, '') [パターン除去]

getEscapedCssVarName(key, doubleEscape)
    │
    └─ key.replace(cssVarNameEscapeSymbolsRE, s => `\\${s}`) [バックスラッシュエスケープ]
```

### データフロー図

```
[入力]                    [処理]                        [出力]

任意の値 ───────▶ escapeHtml() ──────────────▶ エスケープ済み文字列
                       │
                       ├─ 早期リターン最適化
                       │      └─ 特殊文字なし → そのまま返却
                       │
                       └─ charCodeAt走査
                              └─ 特殊文字 → HTMLエンティティに変換

"<script>" ─────▶ escapeHtml() ──────────────▶ "&lt;script&gt;"
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| escapeHtml.ts | `packages/shared/src/escapeHtml.ts` | ソース | escapeHtml()、escapeHtmlComment()、getEscapedCssVarName()の定義 |
| index.ts | `packages/shared/src/index.ts` | ソース | パッケージエクスポート |
| ssrInterpolate.ts | `packages/server-renderer/src/helpers/ssrInterpolate.ts` | ソース | escapeHtml()の利用箇所（テキスト補間） |
| ssrRenderAttrs.ts | `packages/server-renderer/src/helpers/ssrRenderAttrs.ts` | ソース | escapeHtml()の利用箇所（属性レンダリング） |
| render.ts | `packages/server-renderer/src/render.ts` | ソース | escapeHtml()の利用箇所（テキストVNodeレンダリング） |
