Bug(バグ) #3134
完了4バイト文字を含んだ文字列を投稿すると、4バイト文字以降のデータが切れた状態で保存されてしまう
0%
説明
現象¶
MySQL の utf8 では4バイトの文字を扱うことができず、4バイト文字を含む文字列を保存する際に4バイト文字以降が切れてしまった不完全な状態で保存されてしまいます。
この影響で、以下の様な問題が発生します。
- 必須項目が空の投稿がおこなえる
- トピック・日記のタイトルに4バイト文字が含まれていると、通知のアクティビティデータが破損し、そのアクティビティを表示する箇所(マイホーム、アクティビティリストなど)が500エラーになる
500 | Internal Server Error | Doctrine_Table_Exception Unserialization of template_param failed.
UTF-8 の4バイト文字は、JIS X 0213 第3・第4水準漢字の一部や、Unicode 6.0 の携帯電話の絵文字の一部が該当します。
Unicode 6.0 で定義された携帯電話の絵文字は iOS 5 の絵文字としても既に使われていますが、このうち U+10000 以降のものは UTF-8 では4バイト文字となります。
参考¶
UTF-8で4バイトになる文字
http://www.softel.co.jp/blogs/tech/archives/596
Unicode6.0の携帯電話の絵文字の一覧 - Wikipedia
http://ja.wikipedia.org/wiki/Unicode6.0%E3%81%AE%E6%90%BA%E5%B8%AF%E9%9B%BB%E8%A9%B1%E3%81%AE%E7%B5%B5%E6%96%87%E5%AD%97%E3%81%AE%E4%B8%80%E8%A6%A7
iOS Emoji
http://punchdrunker.github.com/iOSEmoji/table_html/
現象確認バージョン¶
OpenPNE-3.6.4
原因¶
修正内容¶
Yuya Watanabe さんが約12年前に更新
メモ¶
対策としては 2通り考えられるが,先に結論を述べておくと 後者を選択する
- 4byte 文字をサポートできる仕様にする
- 4byte 文字を受け付けない仕様にする
前者の場合,データベース自体(この場合 MySQL)が 4byte 文字に対応していないということになり,この MySQL のバージョンアップなりの対応で対応できるが,すでに動いている SNS などを考慮すると MySQL のバージョンを上げることが難しい場合もあるため今回はこの方法を取らない.次期 OpenPNE バージョンなどで対応できるように検討することが適切であるとおもわれる.
基本的に本チケット(本バージョン)では後者の場合を取りうるが,この場合での検討事項は どのタイミングで 4byte 文字を切り落とすかとなる.
この問題はおそらく MySQL のバージョンや使用する文字コードなどが影響しているため Doctrine 以下で行いたいところが,データ格納時などに切り落とす処理を行うと影響範囲が大きく,Symfony で提供されているバリデーションがうまく働かなくなる可能性なども十分にありえるためあまり得策ではないといえる.
OpenPNE では似たような問題として ヌルバイト文字にも対応していて,これはリクエスト時にヌルバイトを切り落とすかどうかを指定して,切り落とした文字列を用いている.本チケットの問題も同様の対処により解決できるのではとかんがえられる.
つまりリクエストから文字列を取得してくる際に 4byte 文字を削除するような対策を取る.
この対策を施した OpenPNE では 4byte 文字を扱えないという仕様になり, 4byte 文字を削除した文字列を入力値として扱う.
こうすることで,空文字列が許容されないフォームに対しても 4byte 文字のみで構成された場合に空文字列として扱わせることで Symfony の Validator をそのまま用いることができる.
Yuya Watanabe さんが約12年前に更新
すでに投稿された 4byte 文字に関係するものはDBに入っていないため考慮する必要はないが,アクティビティ通知のテンプレート部分が不正な場合のアクティビティのを削除するなどの対処を行う必要がある可能性がある.
+----+-----------+-------------------------+----------+-------------------+-------------+-------+-----------+--------+------------+---------------+------------+----------+-------------------------+---------------------+---------------------+ | id | member_id | in_reply_to_activity_id | body | uri | public_flag | is_pc | is_mobile | source | source_uri | foreign_table | foreign_id | template | template_param | created_at | updated_at | +----+-----------+-------------------------+----------+-------------------+-------------+-------+-----------+--------+------------+---------------+------------+----------+-------------------------+---------------------+---------------------+ | 3 | 1 | NULL | [Diary] | @diary_show?id=45 | 1 | 1 | 1 | Diary | NULL | NULL | NULL | diary | a:1:{s:3:"%1%";s:1514:" | 2012-09-12 13:24:11 | 2012-09-12 13:24:11 | +----+-----------+-------------------------+----------+-------------------+-------------+-------+-----------+--------+------------+---------------+------------+----------+-------------------------+---------------------+---------------------+
正しそうなデータは以下のとおり.
+----+-----------+-------------------------+----------------------+-------------------+-------------+-------+-----------+--------+------------+---------------+------------+----------+--------------------------------------+---------------------+---------------------+ | id | member_id | in_reply_to_activity_id | body | uri | public_flag | is_pc | is_mobile | source | source_uri | foreign_table | foreign_id | template | template_param | created_at | updated_at | +----+-----------+-------------------------+----------------------+-------------------+-------------+-------+-----------+--------+------------+---------------+------------+----------+--------------------------------------+---------------------+---------------------+ | 4 | 1 | NULL | [Diary] タイトル | @diary_show?id=46 | 1 | 1 | 1 | Diary | NULL | NULL | NULL | diary | a:1:{s:3:"%1%";s:12:"タイトル";} | 2012-09-12 13:28:29 | 2012-09-12 13:28:29 | +----+-----------+-------------------------+----------------------+-------------------+-------------+-------+-----------+--------+------------+---------------+------------+----------+--------------------------------------+---------------------+---------------------+
Yuya Watanabe さんが約12年前に更新
- Web サーバの外側から入ってくる任意の文字列で対応する必要がある
- OpenPNE の PHP では UTF-8 を用いており,4byte UTF8 についての考慮はなされていない
- 携帯電話のメールは Shift-JIS であり,4byte UTF8 について(おそらく)考慮する必要はない
外からの入力が必ず通る Request あたりで OpenPNE で変更出来る部分として opWebRequest のパラメータ取得を修正しようとした.
pc_frontend アプリケーションの場合ではうまくいくが api アプリケーションではうまくいかない.リクエストのオブジェクトとして opWebRequest を用いていないためである.
sfWebRequest であるならば request.filter_parameters のようなイベントを用いることができるが,これは初期化の時点で発行されるためにこのイベント発行時点での Request の中身を書き換えてしまう必要がある.
正しそうな修正としては api アプリケーションでも opWebRequest を用いることが考えられるが,本チケットの修正を対象とする修正としては少々大きいため,他の動作に影響がないか調査する必要がある.
Kousuke Ebihara さんがほぼ12年前に更新
- ステータス を New(新規) から Pending Review(レビュー待ち) に変更
- 進捗率 を 0 から 50 に変更
Kousuke Ebihara さんがほぼ12年前に更新
opDoctrineRecord のセッター経由で値を設定する際、カラムが非バイナリ文字列をストアするものである場合で、 DBMS として MySQL を使用している場合は、文字列中に含まれる 4 バイト UTF-8 の文字を U+FFFD に置換するように変更しました。
ただし、 MySQL を使用している場合でも 4 バイト UTF-8 に対応可能な環境についてこの挙動を回避できるよう、 Doctrine の接続設定として ATTR_4BYTES_UTF8_READY (999) を受け入れられるようにしました。以下のように設定することで U+FFFD の置換がおこなわれなくなります。
all: doctrine: class: sfDoctrineDatabase param: dsn: 'mysql:dbname=example;host=localhost' username: root encoding: utf8 attributes: { 164: true, 999: true } # 999: true を追加
Rimpei Ogawa さんがほぼ12年前に更新
- ステータス を Pending Review(レビュー待ち) から Pending Testing(テスト待ち) に変更
- 進捗率 を 50 から 70 に変更