技術的な詳細
ビルドについて
Java SDK と antが必要です。
windowsな人はbuild.batに各種のパスを指定してから
コマンドプロンプトで build compile を実行してください。
J2SE1.3以前の場合は
マウスホイールやフォーカス関連の部分のコードを削らないと
うまく動かないと思います。
チャンネル名について
IRCプロトコルのチャンネル名については、以下のいくつかの要因が混乱を生んでいます。
- 制御コードを含むほとんどの文字が扱えてしまうため、
そのまま「表示可能な文字列」にできない場合があります。
- サーバはチャンネル名を比較する際に
半角英字といくつかの記号は大文字と小文字を区別しない
- 日本ではチャンネル名はISO2022JPとして扱いますが、
ISO2022JPとして正しくないチャンネル名もプロトコル上は制限されていません。
- !で始まるセーフチャンネルには5文字のprefixがつくが、
ユーザはprefixを無視して扱いたいと思っている。
しかし、prefixの文字種と同じ文字種だけでチャンネル名を指定した場合には
文脈から推測するのでない限り prefixありの名前かそうでないかを判断できない。
このため、IRCクライアントでは以下のような対処が必要になります。
- サーバから受け取ったチャンネル名を全く変換せずに保持する
- 2つのチャンネル名が同一かどうか比較する際は、
元のバイト列を小文字にしたものを使って比較する
- 正確さよりも短さが優先される文脈では、セーフチャンネルには短い名前を表示する
- ユーザが手でコマンドを入力する際に、制御コードを含む任意のチャンネル名を扱えるようにする。
とくに最後の点をうまく処理するために、チャンネル名をエスケープする規則を
決めました。エスケープされたチャンネル名は
∴!87KRJ%1b$Bくいっく%1b(BIRC
のような形になります。
- エスケープされた部分文字列は∴で始まり、そこから空白又は文の終端までの部分がエスケープの対象となります。
- エスケープされた中に%に続いて2ケタの16進数があった場合、それは2桁の数値によって示されるバイトを表す。
- 日本語(とりあえず現在は c>=0x7e という程度の判定です)が登場したら、それはJIS区点番号(0x21-7fが2つ続く奴ね)の2バイトを表す。
- アンエスケープは設定ファイルのからのチャンネル名の読み込みとユーザが入力したコマンドの内部でしか行われない
- 0x21〜0x7e の内部で、なおかつ % 以外の文字はエスケープする必要はありません。もちろんエスケープしても構いません。
あと、ユーザが手でコマンドを入れる際に
!の短い名前を長い名前に展開できるようにするべきかもしれません。
しかしこれをうまく実現するいいアイデアがないので保留しています。
スレッド間の同期
GUIを扱うプログラムでは呼び出し関係がつねに同じ順序にはなりません。
そのため、Javaで排他するときのおまじない「synchronizedの入れ子関係は常に同じ順序に」
を守るのは難しくなっています。
JavaのGUIライブラリであるSwing(AWT)はこれに対して
「俺に用があるならSwingUtilities.invokeLater(Runnable doRun) を通して呼べ」
と主張しています。可変引数もクロージャもない言語のくせに何いうてんねん。
ところがこの方法にも問題があって、
「Swingが描画を終えるまで待ってからinvokeLaterする」
ことができないのです。おかげで起動時の画面はちょっと見苦しい。
他にいいアイデアもなかったので、アプリケーションのmainスレッドと
TCP接続を読むスレッドは、何かするときには必ずinvokeLaterを通すようにしました。
これでたぶんデッドロックは起きないでしょう。
Swing(AWT)以外のスレッドは自分自身以外何もsynchronizedしていないからです。
見苦しいのは上の問題点のせいでして、安全を犠牲にしない限りいい解決はなさそうです。
ユーザ一覧のダブルクリック
@+ => +o
Nick,prefix => 入力にペースト
文字コード変換
BluntIRCではメッセージやチャンネル名の文字コードはJISでエンコードしてサーバとやり取りすると
仮定されています。
サーバから受け取ったバイト列をJISからUnicodeに変換するときや、
ユーザから受け取った入力をJISに変換したりする際に、
QuickIRC
で使ってたクラスをそのまま利用しています。
prefix
IRCプロトコルでは、メッセージの先頭には送信元をあらわすprefixがつきます。
これはサーバか、送信元のユーザについての情報です。
サーバの場合はそのホスト名そのままか、リレー際にIRCサーバが認識している名称(たとえば *.jp 等)がprefixになります。
ユーザの場合は tateisu!~tateisu@juggler.jp というような形式になります。
! と @ で区切られていて、最初がユーザのニックネーム、最後がユーザのクライアントのホスト名です。
真中の部分にはいくつかの形式があります。
"RFCから読めないIRC"に説明があったのですが、
なし、^~+-=の中から選ばれるようです。
どういった基準で選ばれるかは、
逆引きの有無やident認証の結果や ユーザモード +r(サーバ設定の i 行)
の状態によって変化するようです。
これの基準はサーバによって異なります。
これらの記号を上手く使うと効果的にbanマスクを設定することができますが、
サーバによっては記号がつかないためにたいして利用できないかもしれません。
あるチャンネルにjoinした場合、ユーザのprefixは自動的には一覧には渡されません。
これに問題がある場合、クライアントが自主的にwho クエリを発行することになります
CTCP (未対応)
CTCPについてはサーバ〜クライアント間では定められていません。
また、ドラフトについては
- CTCP/2 のdraft。98年のもの。おそらくIRCnetでは採用されていない。
- http://web.lag.net/~robey/ctcp/
- Michael McLaganによるInternet Draft 。February 2, 1997
- http://www.invlogic.com/irc/ctcp.html
- Klaus Zeuge,Troy Rollo,Ben Mesander による記述。94年のもの。おそらくもっとも広く使われている。 以後CTCP/1と呼ぶことにする。
- http://www.irchelp.org/irchelp/rfc/ctcpspec.html
- 和訳 http://homepage1.nifty.com/YoR/ctcp.new.jp.txt
と、クォートについての指示が異なる複数のものがあって、
どれに対応させるか選ばないといけません。
また、複数のユーザがCTCPを大量に送りつけてくるようなabuseへの対応が必要です。
メッセージの引数のクォートについて
CTCP/1 では、メッセージ全体をCTRL-Pでクォートするように指定しています。
CTRL-Pがクォートするのはnulと改行で、CTCP/2ではさらに空白と%01も含まれています。
ここでいうメッセージとはIRCサーバとやり取りする行のことではなく、
PRIVMSGやNOTICEのtrailのことを指しているようです。
というかそう解釈しないとかなり混乱します。
たとえばチャンネル名に空白や改行やnulや%01が入った場合について考えてみます。
もし生のチャンネル名をユーザに見せないなら、CTRL-P クォートの規則からはずれた
チャンネル名を使われたときにユーザに間違った情報を見せてしまう可能性があります。
ですので、少なくともチャンネル名については私たちは生の情報をユーザに見せるべきです。
クォートに対応しなくても困ることはあまりありませんが、
対応することで安全が失われることがあるわけです。
また、もしクォートしたチャンネル名だけをユーザに見せるなら、ユーザが手でコマンドを入力
するときのチャンネル名はどうなのかという問題も発生してしまいます。
特にCTCP/2では「引数の中の空白はクォートするべきだが引数の間の空白はクォートするべきではない」
という話になっていて、ユーザの入力だけからある空白がどちらの場合になるのかを判断するのは
困難です。
とまあ私にはとても解決できないくらい混乱したので、クォート/デクォートを扱う範囲については
受信についてはPRIVMSG,NOTICEのtrailのみ 送信についてもPRIVMSG,NOTICEのtrailのみということにしました。
CTCP内部の\クォートについて
CTCP/1ではCTCPの内部での%01を \a とクォートするように指定されていますが、
CTCP/2では「こんなん実装してるのは少しのクライアントだけや」という理由で
廃止が提案されていて、かわりにCTRL-Pクォート中で%01とスペースを扱うようになっています。
いくつかのIRCクライアントで試してみましたが、\\ は\\ のまま表示されていることがほとんどでした。
送信するメッセージのクォートについて
- メッセージの引数の中の 空白を %10 '@' にするか?
- もしtrailでない引数に空白が登場するなら、これは必須でしょう。
trail中の空白のクォートについては、必要ではないし現行のクライアントの対応状況から
考えていって行わない方がよいでしょう。
- CTCPの内部の引数の中の空白を %10 '@' にするか?
- CTCPの内部の引数の間の空白を %10 '@' にするか?
- もしtrailでない位置にCTCPを置くのなら、これは必須でしょう。
しかし実際にはCTCPはtrailにしか置かれません。
とりあえず設定で変更できるようにしました。