Shiftt-JISで外字含めた文字の送信例

## はじめに
htmlでcharsetをshift-jisでセットした場合、外字がshift-jisにエンコーディングを確認します。
結果外字の場合はshift-jisが正常にshift-jisのURLエンコーディングに変換されませんでした。
想定通りに行かなかったので、その考察です。

## Shift-JIS使用にあたって
**以下を行っています。**
使用ファイルはShift-JISで保存。
※Eclipseの設定で「Shift-JIS」が選択肢にない場合ので、代わりに「MS932」を選択、「MS932」はWindows環境におけるShift-JISのエンコード名で、実質的に同じもの。
- HTMLフォームで`accept-charset="Shift_JIS"`を設定、MS932(Shift-JIS)としてデータを送信
- サーバー側でリクエストを受け取る際、`request.setCharacterEncoding("Shift_JIS")` を設定
- (Eclipseで)`indexShiftExample.html`が **MS932** で保存されていることを確認、Shift-JISとして認識されていることを確認
- JavaScript(`gaijiShift.js`)もMS932で保存されていることを確認


## Webフォント
外字を表示するためにWebフォントを使用しています(fontTest.woff)。
Webフォント作成ツールのfontforgeを使用しています。
E000に適当な外字を割り当てています。
サーバーサイドは何でもいいですがjavaを使用しています。


## 外字をShift-JISに変換した場合の期待値

#### 前提：
- Shift-JISは、公式に定義されている文字セットに含まれる文字に対して固定のバイト列を持っています。
- 外字（例えば、Unicodeの私用領域：`\uE000`）はShift-JISに直接対応するバイト列が存在しないため、独自のルールでマッピング（変換）する必要があります。
- **カスタムマッピングが必要**。

#### 外字の変換例
例えば、Unicodeの私用領域文字（`U+E000`）をShift-JISのカスタム範囲（例えば、`F040`から始まる領域）に割り当てる場合：

| Unicode | Shift-JISバイト列 | URLエンコード後の期待値 |
|---------|--------------------|------------------------|
| `\uE000` | `0xF0 0x40`         | `%F0%40`              |
| `\uE001` | `0xF0 0x41`         | `%F0%41`              |
| `\uE002` | `0xF0 0x42`         | `%F0%42`              |

このように、外字をShift-JISに対応付ける際には、独自のマッピング表を準備し、それに基づいて変換する必要があります。


## 結果
フロントからサーバーサイドに送信時、外字が含まれているとshift-jisにエンコーディングされませんでした。
開発ツールのネットワークのペイロードで確認できます。
inputText=%82%A0%26%2357344%3B

**%26%2357344%3Bは本来の結果である%F0%40になっていません。**

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/176603/21ea56e5-0cb9-45ca-6c8e-e818aeacb530.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/176603/491de6ac-f21c-0c6a-30b6-d629be3189d5.png)


indexShiftGaijiExample.html
```html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="Shift_JIS">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>外字入力テスト</title>
<style>
@font-face {
	font-family: 'GaijiFont';
	src: url('./fonts/fontTest.woff') format('woff'); /* 外字フォントを指定 */
}

.gaiji-input {
	font-family: 'GaijiFont', sans-serif; /* 外字フォントを適用 */
	font-size: 24px;
}
</style>
</head>
<body>
	<h1>外字入力テスト</h1>
	<form action="/GaijiFontProject/SubmitFormShiftServlet" id="myForm" method="post" accept-charset="Shift_JIS">
		<input type="text" name="inputText" id="inputText" class="gaiji-input"
			placeholder="ここに#を入力してください">
		<button type="submit">送信</button>
	</form>
	<script src="./js/gaijiShift.js"></script>
	<!-- 外部JavaScriptファイルを読み込む -->
</body>
</html>
```
gaijiShift.js
```js
const input = document.getElementById('inputText');

// 入力時の処理
input.addEventListener('input', () => {
    // 入力値を取得
    let value = input.value;

    // # を U+E000 に変換
    value = value.replace(/#/g, '\uE000');

    // 入力欄に変換後の値をセット
    input.value = value;
});

```
HTMLで`Shift_JIS`を指定しているにもかかわらず、外字（`#`が`\uE000`に変換されたもの）がサーバー送信時に正しくShift-JISエンコーディングされていないようです。

## 現在の結果の問題点

結果：
```plaintext
inputText=%82%A0%26%2357344%3B
```

ここで注目すべき点：
1. `%82%A0`は「あ」のShift-JISコードです。これは正常です。
2. `%26%2357344%3B`はHTMLエンティティ形式で、`&#57344;`（Unicodeの`U+E000`）を指しています。外字がShift-JISに変換されず、この形で送信されています。

## 背景
モダンブラウザ（Edge、Chromeなど）は内部的に **UTF-8** を標準として扱っているため、外字（外部文字）が Shift-JIS の URL エンコーディングに変換されないのはその影響によるものらしいです。

http://www.ic.daito.ac.jp/~mizutani/html/default_encode.html


#### 1. **モダンブラウザと文字エンコーディング**
- **モダンブラウザ（Edge、Chrome）では、内部的に UTF-8 を標準文字エンコーディングとして採用**しています。
  - これは、グローバルで統一された文字エンコーディングである UTF-8 を使用することで、多言語対応が容易になるためです。
  - HTML の `meta charset` を `Shift-JIS` に設定しても、モダンブラウザはフォームデータや URL パラメータを UTF-8 エンコーディングで送信します。
  - **Shift-JIS でのエンコーディングを完全にサポートしない**のが一般的です。

####  **ブラウザが内部で UTF-8 を使う理由**
- **一貫性と互換性**  
  - 世界中のウェブサイトが UTF-8 を使用しているため、これをデフォルトにすることで、互換性の問題を減らします。
- **パフォーマンス**  
  - UTF-8 は ASCII と互換性があり、データ転送が効率的です。
- **セキュリティ**  
  - UTF-8 を使用することで、一部の文字エンコーディング（Shift-JIS など）に特有のセキュリティ問題を回避できます。

#### 5. **過去のブラウザ（Internet Explorer）の挙動**
- **Internet Explorer** は、`meta charset="Shift_JIS"` を設定すると、フォームデータや URL を Shift-JIS エンコーディングで送信しました。
  - IE は Shift-JIS の扱いを前提とした日本特有の要件に対応していたためです。
- モダンブラウザが UTF-8 に移行した背景には、こうしたローカル要件よりもグローバル標準を優先する方針が影響しています。

**とはいえ、過去データ等の継承から依然としてShift-JISで対応している所も多いと思われます。**


## 画面表示の場合
特にそのまま取得しても問題ありません。
(**表示の場合に限ります、DBへの登録は検証していません**)
ブラウザは数値文字参照（&#コードポイント;）をUnicodeコードポイントとして解釈するためかと考えられます。
&#57344;(`&#57344;`)の表示でも問題ない。
例)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/176603/76fd8e04-ac8c-1662-65a5-52ea80d2bccd.png)

SubmitFormShiftServlet.java
```java
import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet("/SubmitFormShiftServlet")
public class SubmitFormShiftServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // リクエストの文字エンコーディングをShift-JISに設定
        request.setCharacterEncoding("Shift_JIS");

        // フォームデータを取得
        String rawData = request.getParameter("inputText");
        System.out.println("受信したデータ: " + rawData);
        
        // レスポンスの文字エンコーディングをShift-JISに設定
        response.setContentType("text/html; charset=Shift_JIS");
        response.setCharacterEncoding("Shift_JIS");
        PrintWriter out = response.getWriter();
        out.println("<html>");
        out.println("<head>");
        out.println("<title>Shift-JIS 外字対応</title>");
        out.println("<style>");
        out.println("@font-face { font-family: 'GaijiFont'; src: url('./fonts/fontTest.woff') format('woff'); }");
        out.println(".gaiji { font-family: 'GaijiFont', sans-serif; font-size: 24px; }");
        out.println("</style>");
        out.println("</head>");
        out.println("<body>");
        out.println("<h1>Shift-JISデータ</h1>");
        out.println("<p class=\"gaiji\">" + rawData + "</p>");
        out.println("</body>");
        out.println("</html>");
    }
}

```

request.getParameter("inputText")にて受信したデータ: `あ&#57344;`
(この処理はShift-JISでエンコードされたURLエンコードをUnicodeで取得している」)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/176603/66e2f3c5-508a-3775-a27b-0d013bc3733c.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/176603/ad280ba4-6480-2c7a-c7be-d30e669b57ac.png)

## おわりに
何か問題ありのような記載ですが、実際はそのまま外字がshift-jisにエンコーディングされないまま送信しても氷人は問題ありません。
また機会があればDBへの登録に関して文字コードの観点から確認しようと思います。