2. 文字 (Characters)
本章では、URIで使用される文字セット、エンコーディング機構、および処理規則を定義します。
文字エンコーディングの基礎
URI構文は、データ(リソースの識別に使用される可能性がある)を文字列にエンコードする方法を提供します。
エンコーディング階層:
リソース → URI文字 → オクテット → 転送/ストレージ
文字セット: URIは、数字、文字、およびいくつかのグラフィック記号で構成されるUS-ASCII文字セットに基づいています
2.1. パーセントエンコーディング (Percent-Encoding)
目的
パーセントエンコーディング機構は、オクテットに対応する文字が許可されたセットの外にある場合、または区切り文字として使用されている場合に、コンポーネント内のデータオクテットを表すために使用されます。
エンコーディング形式
pct-encoded = "%" HEXDIG HEXDIG
形式: パーセント文字 "%" の後に、そのオクテットの数値を表す2桁の16進数が続きます
例
| 文字 | 2進数 | 16進数 | パーセントエンコード |
|---|---|---|---|
| スペース | 00100000 | 0x20 | %20 |
| ! | 00100001 | 0x21 | %21 |
| # | 00100011 | 0x23 | %23 |
| あ (日本語) | - | 0xE38182 | %E3%81%82 |
大文字小文字規則
等価性: 大文字の16進数字 'A' から 'F' は、小文字の数字 'a' から 'f' と等価です
正規化: パーセントエンコードされたオクテットで使用される16進数字の大文字小文字のみが異なる2つのURIは等価です
推奨事項: URIプロデューサーとノーマライザーは、すべてのパーセントエンコーディングに大文字の16進数字を使用すべきです (SHOULD)
推奨: %2F %3A %5B
非推奨: %2f %3a %5b
2.2. 予約文字 (Reserved Characters)
定義
URIには、「予約」セット内の文字で区切られたコンポーネントとサブコンポーネントが含まれます。これらの文字は、区切り文字として定義される可能性がある(または定義されない可能性がある)ため、「予約」と呼ばれます。
予約文字セット
reserved = gen-delims / sub-delims
gen-delims = ":" / "/" / "?" / "#" / "[" / "]" / "@"
sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
/ "*" / "+" / "," / ";" / "="
分類
汎用デリミタ (gen-delims)
| 文字 | 目的 | 例 |
|---|---|---|
| : | スキームとオーソリティの区切り | http: |
| / | パス区切り文字 | /path/to/resource |
| ? | クエリ区切り文字 | ?key=value |
| # | フラグメント区切り文字 | #section |
| [ ] | IPv6アドレス境界 | [2001:db8::1] |
| @ | ユーザー情報区切り文字 | user@host |
サブデリミタ (sub-delims)
| 文字 | 一般的な用途 |
|---|---|
| ! $ ' ( ) * | パスまたはクエリ内のサブコンポーネント区切り |
| + | スペースの代替表現 |
| , | リスト区切り |
| ; | パラメータ区切り |
| = | キーと値の区切り |
| & | クエリパラメータ区切り |
エンコーディング規則
競合処理: URIコンポーネントのデータが予約文字の区切り文字としての目的と競合する場合、URIを形成する前に競合するデータをパーセントエンコードしなければなりません (MUST)
例:
"?"文字を含むパス:
元: /path/file?.txt
エンコード: /path/file%3F.txt
"&"文字を含むクエリ:
元: ?name=Tom&Jerry
正しい: ?name=Tom%26Jerry (&が区切り文字でない場合)
または: ?name=Tom&name=Jerry (&が区切り文字の場合)
等価性
重要: 予約文字を対応するパーセントエンコードされたオクテットに置き換える点で異なるURIは等価ではありません
http://example.com/path?key=value
http://example.com/path%3Fkey=value
これら2つのURIは等価ではありません
2.3. 非予約文字 (Unreserved Characters)
定義
URIで許可されているが、予約された目的を持たない文字は、非予約文字と呼ばれます。
非予約文字セット
unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
含まれるもの:
- ALPHA: 大文字と小文字 (A-Z, a-z)
- DIGIT: 10進数字 (0-9)
- -: ハイフン
- .: ピリオド
- _: アンダースコア
- ~: チルダ
エンコーディング規則
等価性: 非予約文字を対応するパーセントエンコードされたUS-ASCIIオクテットに置き換える点で異なるURIは等価です
正規化: 非予約文字に対応するパーセントエンコードされたオクテットはデコードすべきです (SHOULD)
等価なURI:
http://example.com/~user
http://example.com/%7Euser
正規化後:
http://example.com/~user
パーセントエンコーディング範囲
作成すべきでない (SHOULD NOT):
- ALPHA:
%41-%5A(A-Z),%61-%7A(a-z) - DIGIT:
%30-%39(0-9) - ハイフン:
%2D - ピリオド:
%2E - アンダースコア:
%5F - チルダ:
%7E
デコードすべき: URI内でこれらのエンコーディングが見つかった場合、ノーマライザーは対応する非予約文字にデコードすべきです (SHOULD)
2.4. エンコードまたはデコードのタイミング (When to Encode or Decode)
エンコードのタイミング
URIプロデューサー:
- URIを生成する際、許可されていない文字をパーセントエンコードしなければなりません (MUST)
- 予約文字は区切り文字として使用される場合のみエンコードされません
- 非予約文字はエンコードすべきではありません (SHOULD NOT)
例:
# パスのエンコード
path = "/files/my document.pdf"
encoded = "/files/my%20document.pdf"
# クエリのエンコード
query = "?name=John Doe&age=30"
encoded = "?name=John%20Doe&age=30"
デコードのタイミング
URIコンシューマー:
- URIを解析した後、必要に応じてコンポーネントをデコードします
- 早すぎるデコードは避けてください(URI構造が変わる可能性があります)
- 各コンポーネントを1回だけデコードします
危険な例:
元: /path%2Fto%2Ffile
早すぎるデコード: /path/to/file (パス構造が変わりました!)
正しい: 最初に解析し、次に各セグメントをデコード
セグメント1: "path%2Fto%2Ffile" → デコード → "path/to/file"
二重エンコーディング問題
元データ: "100%"
最初のエンコード: "100%25"
誤った2回目のエンコード: "100%2525"
デコード時:
"100%2525" → "100%25" → "100%"
2.5. データの識別 (Identifying Data)
文字セットとエンコーディング
文字 vs オクテット:
- URIは文字列です
- 文字は転送/ストレージのためにオクテットとしてエンコードされます
- UTF-8が推奨される文字エンコーディングです
国際化リソース識別子 (IRI)
IRI拡張: RFC 3987はIRIを定義し、Unicode文字の使用を許可します
変換:
IRI: http://例え.jp/引き出し
↓ UTF-8にエンコードしてパーセントエンコード
URI: http://xn--r8jz45g.jp/%E5%BC%95%E3%81%8D%E5%87%BA%E3%81%97
ベストプラクティス
URI生成:
- 非ASCII文字をUTF-8でエンコードします
- 結果のオクテットをパーセントエンコードします
- 大文字の16進数字を使用します
- 非予約文字はエンコードしません
URI消費:
- コンポーネントごとに解析します
- パーセントエンコーディングをデコードします
- UTF-8を使用してオクテットを解釈します
- 無効なエンコーディングを処理します
文字セットクイックリファレンス
完全な文字分類
URI文字
├── 非予約文字 (unreserved)
│ ├── ALPHA: A-Z, a-z
│ ├── DIGIT: 0-9
│ └── 記号: - . _ ~
│
├── 予約文字 (reserved)
│ ├── 汎用デリミタ (gen-delims): : / ? # [ ] @
│ └── サブデリミタ (sub-delims): ! $ & ' ( ) * + , ; =
│
└── パーセントエンコード (pct-encoded): %HEXDIG HEXDIG
エンコーディング決定木
文字をURIに含める必要がある?
├─ 非予約文字? → 直接使用
├─ 予約文字?
│ ├─ 区切り文字として使用? → 直接使用
│ └─ データとして使用? → パーセントエンコード
└─ その他の文字? → パーセントエンコード
一般的な文字エンコーディング表
| 文字 | 目的 | エンコーディング |
|---|---|---|
| スペース | 区切り | %20 または + (クエリ内) |
| ! | サブデリミタ | %21 (エンコードが必要な場合) |
| " | 引用符 | %22 |
| # | フラグメント区切り | %23 (データ内) |
| $ | サブデリミタ | %24 (エンコードが必要な場合) |
| % | エンコーディングマーカー | %25 |
| & | パラメータ区切り | %26 (データ内) |
| ' | サブデリミタ | %27 (エンコードが必要な場合) |
| ( ) | サブデリミタ | %28 %29 |
| + | スペース/サブデリミタ | %2B (データ内) |
| , | リスト区切り | %2C (エンコードが必要な場合) |
| / | パス区切り | %2F (データ内) |
| : | スキーム区切り | %3A (データ内) |
| ; | パラメータ区切り | %3B (エンコードが必要な場合) |
| = | キー値区切り | %3D (エンコードが必要な場合) |
| ? | クエリ区切り | %3F (データ内) |
| @ | ユーザー情報区切り | %40 (データ内) |
| [ ] | IPv6境界 | %5B %5D |
実装の推奨事項
エンコーディング実装
def percent_encode(text, safe=''):
"""テキストをパーセントエンコードする"""
result = []
for char in text:
if char in safe or is_unreserved(char):
result.append(char)
else:
# UTF-8エンコードしてパーセントエンコード
for byte in char.encode('utf-8'):
result.append(f'%{byte:02X}')
return ''.join(result)
def is_unreserved(char):
"""文字が非予約文字かどうかをチェック"""
return (char.isalnum() or
char in '-._~')
デコーディング実装
def percent_decode(text):
"""テキストをパーセントデコードする"""
result = bytearray()
i = 0
while i < len(text):
if text[i] == '%' and i + 2 < len(text):
try:
byte = int(text[i+1:i+3], 16)
result.append(byte)
i += 3
except ValueError:
result.extend(text[i].encode('utf-8'))
i += 1
else:
result.extend(text[i].encode('utf-8'))
i += 1
return result.decode('utf-8', errors='replace')
次の章: 3. 構文コンポーネント (Syntax Components) - URIの構造コンポーネント