Geminiプロトコル仕様実装 - ha1t/php-gm-server GitHub Wiki

Geminiプロトコル仕様実装

本ページは、PHP GM Serverが実装するGeminiプロトコル仕様の詳細を説明します。Geminiはインターネットの古い通信プロトコルからインスピレーションを受け、シンプルで最小限のTLS必須プロトコルとして設計されています。本プロジェクトでは、Geminiプロトコルの基本仕様に準拠し、テキストコンテンツとメディアファイルの配信を実装しています。

プロトコル概要

Geminiプロトコルは、クライアント・サーバー型の通信プロトコルで、以下の特徴があります。

  • TLS/SSL必須化: 全ての通信は暗号化されなければなりません。
  • シンプルな仕様: リクエストとレスポンスの形式が明確かつ単純です。
  • 最小限の設計: 余分な機能を削ぎ落とし、必要最小限の機能で構成されています。
  • テキストベース通信: リクエストとレスポンスはテキスト形式です。
sequenceDiagram
    actor Client as クライアント
    participant TLS as TLS接続
    participant Server as Geminiサーバー
    participant Parser as RequestParser
    participant Handler as StaticFileHandler
    participant Builder as ResponseBuilder

    Client->>TLS: gemini://host/path\r\n
    TLS->>Server: TLS/SSL暗号化
    Server->>Parser: リクエスト解析
    Parser->>Handler: パス検証
    Handler->>Builder: ファイル読み込み
    Builder->>TLS: STATUS META\r\nBODY
    TLS->>Client: TLS復号化
    Client->>Client: コンテンツ表示

リクエスト仕様

Geminiクライアントは、サーバーに対してテキスト形式のリクエストを送信します。

リクエスト形式

gemini://host/path\r\n

リクエストは以下の要素で構成されます。

要素 説明
gemini:// スキーム(固定)
host サーバーのホスト名またはIPアドレス
/path リクエストするリソースのパス
\r\n キャリッジリターンとラインフィード(CRLF)

リクエスト長制限

リクエスト全体の最大長は1024バイトに制限されています。これにはスキーム、ホスト、パス、および終端の\r\nが含まれます。この制限は、サーバーリソースの過剰使用を防ぎ、Geminiの最小限設計哲学を体現しています。

リクエストパース処理

RequestParserコンポーネントクラスが全てのリクエストを解析します。パース処理は以下の手順で実行されます。

  1. CRLF検出: 受信バッファから\r\nの位置を特定します。
  2. 長さチェック: リクエスト行が1024バイトを超えていないか検証します。
  3. スキーム検証: スキームがgeminiであることを確認します。他のスキーム(httpshttpなど)は拒否されます。
  4. ホスト抽出: URLパース結果からホスト名を抽出します。ホスト名が空の場合はパース失敗です。
  5. パス抽出: URLパース結果からパスを抽出します。パスが空の場合は/を設定します。
public static function parse(string $raw): ?array
{
    $line = explode("\r\n", $raw, 2)[0];

    if ($line === '' || strlen($line) > self::MAX_REQUEST_LENGTH) {
        return null;
    }

    $parts = parse_url($line);

    if ($parts === false) {
        return null;
    }

    $scheme = $parts['scheme'] ?? '';
    if ($scheme !== 'gemini') {
        return null;
    }

    $host = $parts['host'] ?? '';
    if ($host === '') {
        return null;
    }

    $path = $parts['path'] ?? '/';
    if ($path === '') {
        $path = '/';
    }

    return [
        'host' => $host,
        'path' => $path,
    ];
}

出所: src/RequestParser.php:14-47

パース失敗時はnullが返却され、サーバーは59ステータスコード(不正リクエスト)でレスポンスします。

リクエスト検証例

有効なリクエスト:
  gemini://example.com/hello.gmi\r\n
  gemini://example.com/\r\n
  gemini://example.com\r\n

無効なリクエスト:
  https://example.com/\r\n (スキーム不正)
  gemini:///\r\n (ホスト不在)
  gemini://example.com/a...a\r\n (1024バイト超過)

レスポンス仕様

サーバーがクライアントのリクエストを処理した後、Gemini形式のレスポンスを返送します。

レスポンス形式

STATUS SPACE META\r\n
BODY

レスポンスは以下の要素で構成されます。

要素 説明
STATUS 2桁の数値ステータスコード
SPACE 単一のスペース文字(0x20)
META ステータスに関連する追加情報(MIMEタイプなど)
\r\n キャリッジリターンとラインフィード
BODY オプション。成功時のみ、リソース本体が続きます

サポートステータスコード

本実装では、以下の3つのステータスコードをサポートしています。

20 成功(Success)

リクエストが成功し、リソースを返送する場合に使用します。

形式: 20 MIME_TYPE\r\nBODY

  • META欄にはMIMEタイプが指定されます。
  • その後、改行を挟んでレスポンスボディが続きます。
  • ボディは、バイナリデータ、テキストコンテンツ、またはメディアファイルの内容です。
20 text/gemini\r\n
# Welcome to Gemini

This is a Gemini document.

51 見つからない(Not Found)

リクエストされたリソースがサーバー上に存在しない場合に使用します。

形式: 51 Not found\r\n

  • META欄には固定文字列「Not found」が指定されます。
  • レスポンスボディは存在しません。

59 不正リクエスト(Bad Request)

リクエスト形式が不正である場合に使用します。

形式: 59 REASON\r\n

  • META欄には不正の理由を示すテキストが指定されます(例:「Request too long」、「Invalid request」)。
  • レスポンスボディは存在しません。

レスポンス構築処理

ResponseBuilderコンポーネントクラスが全てのレスポンスを構築します。

public static function success(string $mime, string $body): string
{
    return "20 {$mime}\r\n{$body}";
}

public static function notFound(): string
{
    return "51 Not found\r\n";
}

public static function badRequest(string $reason = 'Bad request'): string
{
    return "59 {$reason}\r\n";
}

出所: src/ResponseBuilder.php:9-22

MIMEタイプ仕様

ファイルは、拡張子に基づいてMIMEタイプが自動判定されます。本実装でサポートされるMIMEタイプは以下の通りです。

拡張子 MIMEタイプ 用途
.gmi.gemini text/gemini Geminiテキスト文書(Gemini形式)
.txt text/plain プレーンテキスト
.html text/html HTML文書
.css text/css スタイルシート
.js text/javascript JavaScriptコード
.json application/json JSON形式データ
.png image/png PNG画像
.jpg.jpeg image/jpeg JPEG画像
.gif image/gif GIF画像
.svg image/svg+xml SVG画像
.pdf application/pdf PDF文書
その他 application/octet-stream バイナリデータ(デフォルト)

MIMEタイプマップは、StaticFileHandlerコンポーネントで定義されています。

private const MIME_MAP = [
    'gmi' => 'text/gemini',
    'gemini' => 'text/gemini',
    'txt' => 'text/plain',
    'html' => 'text/html',
    'css' => 'text/css',
    'js' => 'text/javascript',
    'json' => 'application/json',
    'png' => 'image/png',
    'jpg' => 'image/jpeg',
    'jpeg' => 'image/jpeg',
    'gif' => 'image/gif',
    'svg' => 'image/svg+xml',
    'pdf' => 'application/pdf',
];

出所: src/StaticFileHandler.php:11-25

拡張子がマップに含まれていない場合は、デフォルトのapplication/octet-streamが使用されます。

実装の対応範囲と制限事項

対応範囲

本実装は、Geminiプロトコルの基本仕様に準拠した範囲で以下をサポートしています。

  1. TLS/SSL通信: 自己署名証明書による暗号化通信
  2. リクエストパース: geminiスキーム、ホスト名、パスの抽出と検証
  3. 静的ファイル配信: ドキュメントルートからのファイル取得
  4. MIMEタイプ自動判定: 拡張子に基づくMIMEタイプの決定
  5. ディレクトリアクセス処理: index.gmiファイルの自動返却
  6. セキュリティ機能: パストラバーサル攻撃の防止

制限事項

以下の機能は、本実装では対応していません。

  1. クエリ文字列: リクエストURLにクエリ文字列(?以降)は含まれない設計です。
  2. リダイレクト: 30番台のリダイレクトステータスコードは実装されていません。
  3. 入力フォーム: 10番台のステータスコードに対応した入力フォーム機能は未実装です。
  4. 認証: ユーザー認証の機構は含まれていません。
  5. ロギング: アクセスログの詳細な記録機能は制限的です。
  6. キャッシング: ファイルキャッシング機構は実装されていません。

これらの制限事項は、プロジェクト概要に記載された、PHP勉強会での発表と個人サイト公開を目的とした最小限設計によるものです。

リクエスト・レスポンス処理フロー

flowchart TD
    A[TLS接続受け入れ] --> B[バッファへのデータ蓄積]
    B --> C{\r\nを検出?}
    C -->|いいえ| D{長さ > 1024?}
    D -->|はい| E[59: Request too long]
    D -->|いいえ| B
    C -->|はい| F[RequestParserでパース]
    F --> G{パース成功?}
    G -->|いいえ| H[59: Invalid request]
    G -->|はい| I[StaticFileHandlerでファイル取得]
    I --> J{ファイル存在?}
    J -->|いいえ| K[51: Not found]
    J -->|はい| L[ResponseBuilder.success生成]
    E --> M[レスポンス送信]
    H --> M
    K --> M
    L --> M
    M --> N[接続終了]

セキュリティ考慮事項

リクエスト長制限

1024バイトの制限により、メモリを消費するDoS攻撃を軽減します。詳細はエラーハンドリング戦略ページを参照してください。

パストラバーサル防止

リクエストパスは、realpath()による正規化後、ドキュメントルート内に収まることが検証されます。これにより、../を含むパストラバーサル攻撃を防止します。詳細はセキュリティ設計ページを参照してください。

スキーム検証

geminiスキーム以外のリクエストは、パース時点で拒否されます。

テスト対応

プロトコル仕様の実装は、以下のテストクラスでカバーされています。

テスト実行方法は、テスト戦略と実装ページを参照してください。

参考仕様

本実装は、以下の外部仕様を参考としています。

  • Gemini Specification: 公式のGeminiプロトコル仕様
  • MIME Types: RFC 2045, RFC 2046で定義されるMIMEタイプ
  • URI/URL: RFC 3986で定義されるURI形式

Related Pages