背景
ある日、サーバーから招待コードを受け取り、そのコードを使ってエミュレータで次の操作を行うプログラムを作成しました。初めての正式なプログラムでしたが、ユーザーの行動に対する懸念はありました(それでも不十分でした)。サーバーメッセージを受信後、メッセージの前後と中間のスペースを削除しました(このシナリオではメッセージに中間スペースが存在するべきではありません)。テストが完了し問題がなければ、安心して寝てしまいました...
問題発覚
翌朝、スマートフォンを開いたら苦情が表示されていました。
ユーザー:「ウェブサイトに入力したのに全く効果がないです、低評価です!!!」
OS:「いや、私は何度もテストしたのに、ほぼ(このほぼに注意)すべてのケースで正常に動作しているはず。どうして問題が起こるんだろう。」と返信し、「アクティベーションコードを間違えて入力したのではないか?」と尋ねました。
ユーザー:「あり得ない、絶対にあり得ない。私は直接コピー&ペーストしたんだ!」
少し不安になり、ユーザーのアクティベーションコードを尋ねてログを確認しましたが、完全に同じコードの記録は見つかりませんでした。困惑し、1文字ずつ削除して関連する記録を見つけようと試みました。努力は裏切りませんでした。
受信したアクティベーションコード: 1b 636 e 1 a ERROR - api - 363: WebSocketメッセージ処理エラー: 'gbk' codec can't encode character '\u2006' in position 82: illegal multibyte sequence
招待コードの中のスペースを見て、自分のスペース処理コードを見て、その下のERRORログを見て、言葉を失いました。ゲームから招待コードをコピーするなぜこのような問題が発生するのか理解できませんでした。シナリオを再現したところ、LDPlayerエミュレータで以下のコマンドを実行すると、特殊文字(\\u2006)が存在するために失敗することがわかりました。
ldconsole adb --index 0 --command "shell input text '入力するテキスト'"
解決策
最終的に、コードにスペースを削除した後に特殊文字を削除する機能を追加しました。
# コード内の不可視文字を削除
async def 文字列をサニタイズ(テキスト: Union[str, any]) -> str:
"""
すべての不可視Unicode文字(\u2006、ゼロ幅スペース、制御文字など)を削除します
Args:
テキスト: 入力文字列
Returns:
サニタイズされた文字列。非文字列型はそのまま返します
"""
if not isinstance(テキスト, str):
return テキスト
# 1. NFKC正規化(特殊なスペースを通常のスペースに変換)
テキスト = unicodedata.normalize('NFKC', テキスト)
# 2. すべての不可視文字を削除
# \p{C}: その他(制御文字、書式文字、サロゲートなど)
# \p{Z}: 区切り文字(スペース、行区切りなど)
# \u2000-\u200F, \u2028-\u202F, \u205F, \u3000, \uFEFF などを含む
テキスト = re.sub(r'[\\p{C}\\p{Z}]', '', テキスト, flags=re.UNICODE)
テキスト = テキスト.replace(" ",'')
return テキスト
結論
ついにユーザー入力の処理が完了し、プログラムは正常に動作するようになりました。これからは二度とユーザーの入力を信用しないと決めました(いや、この特殊文字は一体どこから来るんだ)
(本当に解決したのだろうか、続く...)