BoardGuard DSL

1. 概要

BoardGuard DSL(以下 DSL)は、Perl 準拠の構文で、条件付き規制を行えるツールです。各ルールは個別のサブルーチンとして定義されます。


2. ファイル構造

DSL ファイル全体は、以下の要素から構成されます。

  1. トップレベルコード(オプション)
    • my/our などの変数定義を自由に書けます。
    • ただし Safe コンパートメント上で動作するため、許可されていない関数呼び出し(system, open など)は実行時に制限されます。
  2. ルール定義(必須)
    • ルールごとに以下のような形式で記述します:
      Rule<RuleName> sub {
          # $ctx 参照で入力値を読み取り。$outは変更の反映用
          my ($ctx, $out) = @_;
          # 処理内容
          return _DENY_;   # または _ACCEPT_。省略された場合次のルールの評価に移る( _PASS_ )。
      }
      
    • 複数ルールを順次定義できます。
    • ルール名 (RuleName) は英数字とアンダースコアのみを含む単語(先頭に数字不可)とします。

3. トップレベルコード

3.1 ヘルパー関数の定義

  • ファイル冒頭(ルール定義の前)に、共通で利用するヘルパー関数 (sub HelperFoo { … }) を記述できます。
  • 例:
    # DSL ヘルパー例
    sub HelperIsAdmin {
        my ($ctx) = @_;
        return ($ctx->{user_info}{is_admin} // 0) == 1 ? 1 : 0;
    }
    
  • Safe コンパートメント上で一度だけ評価されます。複数ルール間で共通利用できます。

3.2 変数定義(グローバル or my/our)

  • トップレベルで myour を使って変数を定義しても問題ありません。ただし Safe 上で実行されるため、外部への影響はありません。
    my $count = 0;
    our $VERSION = '1.0';
    
  • 定義された変数は、同一ファイル内のすべてのルール定義で参照可能です。

4. ルール定義の文法

4.1 基本形

各ルールは必ず次の形で書きます。ルール名は Rule プレフィックス+英数字文字列とします。

Rule<RuleName> sub {
    my ($ctx, $out) = @_;

    # ルールの処理内容を書く
    # 最終的に _DENY_ か _ACCEPT_ を return する。省略された場合または _PASS_ で次のルールに移行。

    if ( 条件 ) {
        # 何らかの出力設定は %out に書き込む
        $out->{error_code}    = $E_FORM_LONGSUBJECT;
        return _DENY_;
    }

    # 条件を満たさなければ許可
    return _ACCEPT_;
}
  • Rule<RuleName>
    • <RuleName> 部分は Rule に続く英数字・アンダースコアのみとします。例:RuleSpamDetect, RuleAdminOnly など。
    • 先頭文字はアルファベットで始めてください。
  • sub { … }
    • ルール本文は無名サブルーチン(sub { ... })で囲みます。
    • 引数は必ず (my ($ctx) = @_) の形で、外部から渡された %ctx を読み取ります。

4.2 %ctx%out の扱い

  • 読み取り専用の %ctx
    DSL 実行時に、外部(アプリケーション側)から以下のキーを含むハッシュリファレンス %ctx が渡されます。

    キー名 内容
    message 投稿本文
    mail メール欄またはコマンド欄
    name 名前欄
    title スレ立て時のタイトル
    time 投稿時刻(タイムスタンプ)
    thread_id スレッド ID(スレ投稿時)
    thread_title スレッドタイトル(2レス目以降)
    bbs 掲示板ディレクトリ名
    fp フィンガープリント
    ip 投稿者 IP アドレス
    host ホスト名
    ua ユーザーエージェント
    session_id セッション ID(忍法帖 ID)
    cap_id キャップ ID
    setting 掲示板設定情報のハッシュリファレンス
    attr スレッド属性情報のハッシュリファレンス
    user_info ユーザ情報のハッシュリファレンス(忍法帖情報など)
    score スコア管理用数値
    unique 独自拡張用のハッシュリファレンス(空または追加値)
    • ルール内で $ctx->{キー名} を読み取り、判定ロジックに利用します。
    • $ctx を書き換えても呼び出し元に影響はありません。
  • 書き込み先の %out
    ルール内で結果をアプリケーション側に返すときは、%out に必要なキーを書き込みます。

    キー名 内容
    error_code エラー時に返す整数コード($ZP::… 定数など)
    error_message エラーメッセージの文字列
    error_subject エラー件名(コマンドエラー時に出す件名)
    message 投稿本文の書き換え結果
    mail メール欄の書き換え結果
    name 名前欄の書き換え結果
    title スレタイの書き換え結果(スレ立て時のみ)
    thread_updown フロート制御(top/bottom/down/age/sage/[±数値])
    attr スレッド属性ハッシュリファレンス
    user_info ユーザ情報ハッシュリファレンス
    unique 独自拡張用ハッシュリファレンス

    例:

    $out->{error_code}    = 100000;
    $out->{error_subject}    = "長文規制!";
    $out->{error_message} = "長文を書くには忍法帖レベルが足りません。";
    return _DENY_;
    
  • エラーコードについて
    error_codeにはmodule/constant.plにて定義されている各定数が使えます。
    オリジナルのエラー内容にする場合は、error_codeに既存のエラーコード以外の値を入れた上で、error_subjecterror_messageにエラーメッセージをセットしてください。

4.3 戻り値(DENY / ACCEPT / PASS

  • _DENY_:定数 0 を返し、当該ルールで拒否判定を行う。以降のルール評価はスキップされる。
  • _ACCEPT_:定数 1 を返し、書き込みを許可する。以降のルール評価はスキップされる。
  • _PASS_:定数 2 を返し、次のルールの評価に移る。

すべてのルールがreturnに到達しなかった場合、DSL 全体の戻り値は 1(許可)となる。

注意

  • いずれかのルールが _DENY_ または _ACCEPT_を返すと、それ以降のルールは評価されず、即座に結果が返される。
  • ルール内で例外・エラー(タイムアウトや未定義メソッド呼び出しなど)が発生した場合、当該ルールだけをスキップし、次のルールへ進みます。

5. ルール名および重複チェック

  • ルール名の命名規則
    • Rule プレフィックスに続く名前部分は、先頭にアルファベット、続いて英数字・アンダースコアのみを使えます。
      • 例:RuleSpamDetectRuleLengthCheckRuleAdminOnly
    • ルール名には空白、記号(アンダースコア以外)、全角文字は禁止です。
  • 重複ルール名の扱い
    • 同じファイル内で同一のルール名を複数定義すると、構文チェックモードではステータス 4(重複ルール名エラー)となり、DSL 評価時でも最初のエラー検出状態となることがあります。
    • 重複ルール名を避けるよう記述してください。

6. コメント

  • Perl と同様に、以下の形式でコメントを書けます。
    • 行末コメント:# 以降は無視
      RuleFoo sub {
          my ($ctx) = @_;   # 引数の受け取り
          
      }
      
    • 複数行コメント(ヒアドキュメント状にはできない。単純に各行に # を付与)
      # ここは複数行コメント
      # すべて '#' を先頭に置く
      

7. 正規表現の扱い

  • ルール内で Perl の正規表現(m//, qr//, s///, /…/)が使えます。
  • ただし正規表現に文法エラー(未閉鎖スラッシュ、エスケープ不足など)があると Safe 上の評価でエラーとなり、正規表現文法エラーとして扱われます。

例:

RuleSpamDetect sub {
    my ($ctx) = @_;
    # 禁止ワードのリストを正規表現でチェック
    if ($ctx->{message} =~ /spam\d+/) {
        return _DENY_;
    }
    return _ACCEPT_;
}

8. サンプル DSL ファイル例

#------------------------------------------------------------------------------#
# Helper 定義(任意)
#------------------------------------------------------------------------------#
# 忍法帖データのユーザー説明欄`user_desc`に「荒らし」というワードが入っていた場合真となる関数
sub HelperIsArashi {
    my ($ctx) = @_;
    return (($ctx->{user_info}{user_desc} =~ /荒らし/) ? 1 : 0;
}

#------------------------------------------------------------------------------#
# ルール定義
#------------------------------------------------------------------------------#

# ルール1: 禁止ワードチェック
RuleSpamDetect sub {
    my ($ctx, $out) = @_;
    # 「spam123」, 「badword」, 「暴言」のいずれかが含まれていれば拒否
    my @blacklist = qw(spam123 badword 暴言);
    foreach my $w (@blacklist) {
        if ($ctx->{message} =~ /\Q$w\E/i) {
            return _DENY_;
        }
    }
}

# ルール2: タイトル文字数チェック(スレ立て時のみ)
RuleTitleLength sub {
    my ($ctx, $out) = @_;
    if (defined $ctx->{title} && length($ctx->{title}) > 50) {
        $out->{error_code}    = 100001;
        $out->{error_subject}    = "スレタイなげーぞ";
        $out->{error_message} = "端的に書けや";
        return _DENY_;
    }
}

# ルール3: 荒らしに適当なエラーメッセージを見せる
RuleNoArashi sub {
    my ($ctx, $out) = @_;
    if ( HelperIsArashi($ctx) ) {
        srand($ctx->{time});
        $out->{error_code}    = 600+int(rand(5));
        return _DENY_;
    }
}

#------------------------------------------------------------------------------#
# 任意のトップレベルコード
#------------------------------------------------------------------------------#
# 変数 $counter を共通で使いたい場合など
my $counter = 0;

9. 注意事項

  1. Safe コンパートメントの制限
    • DSL の中では system, open, fork などの危険な関数は呼べません。
    • 安全上、ファイルI/O や外部コマンド実行が必要な場合は直接実装してください。
  2. トップレベルで Perl モジュールを use するとエラーになることがある
    • Safe 上で実行されるため、トップレベルで use Some::Module; のようにモジュールをロードしても、Safe の許可リストにない機能を呼ぼうとするとエラーになります。
    • 可能な限りヘルパー関数は最小限の標準機能にとどめてください。
  3. DSL側で任意の変更を行って$outにより反映させる場合
    • 特にmassage,name,mail,titleについて、<,>等htmlとして解釈される文字を通常のテキストとして使う場合は&gt;,&ltなど適切にエスケープしてください。特に<>はdatの区切り文字であるため、原則使用するべきではありません。また改行コード\nもdat構造を破壊するので注意してください。
    • 可能な限り関数は最小限の標準機能にとどめてください。