プロジェクト

全般

プロフィール

Bug(バグ) #3134

4バイト文字を含んだ文字列を投稿すると、4バイト文字以降のデータが切れた状態で保存されてしまう

Kiwa Sakai11年以上前に追加. 約7年前に更新.

ステータス:
Won't fix(対応せず)
優先度:
Normal(通常)
担当者:
対象バージョン:
開始日:
2012-07-26
期日:
進捗率:

0%

3.6 で発生するか:
Yes (はい)
3.8 で発生するか:
Yes (はい)

説明

現象

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

原因

修正内容


関連するチケット

関連している OpenPNE 3 - Backport(バックポート) #3179: 4バイト文字を含んだ文字列を投稿すると、4バイト文字以降のデータが切れた状態で保存されてしまう Fixed(完了) 2012-07-26
関連している OpenPNE 3 - Backport(バックポート) #3255: 4バイト文字を含んだ文字列を投稿すると、4バイト文字以降のデータが切れた状態で保存されてしまう Fixed(完了) 2012-11-08

関係しているリビジョン

リビジョン 61b427a8 (差分)
Kousuke Ebihara11年以上前に追加

changed opDoctrineRecord::_set() to replace 4 bytes UTF-8 characters to U+FFFD in MySQL non binary string column (refs #3134)

履歴

#1 Kiwa Sakai11年以上前に更新

  • 説明 を更新 (diff)

#2 Kiwa Sakai11年以上前に更新

  • 説明 を更新 (diff)

#3 Kiwa Sakai11年以上前に更新

  • 説明 を更新 (diff)

#4 Kousuke Ebihara11年以上前に更新

  • 3.8 で発生するかYes (はい) にセット

#5 Yuma Sakata11年以上前に更新

  • 対象バージョンOpenPNE 3.8.2 にセット

#6 Yuma Sakata11年以上前に更新

  • 対象バージョンOpenPNE 3.8.2 から OpenPNE 3.9.0-old に変更

#7 Yuya Watanabe11年以上前に更新

  • 担当者Yuya Watanabe にセット

#8 Yuya Watanabe11年以上前に更新

メモ

対策としては 2通り考えられるが,先に結論を述べておくと 後者を選択する

  1. 4byte 文字をサポートできる仕様にする
  2. 4byte 文字を受け付けない仕様にする

前者の場合,データベース自体(この場合 MySQL)が 4byte 文字に対応していないということになり,この MySQL のバージョンアップなりの対応で対応できるが,すでに動いている SNS などを考慮すると MySQL のバージョンを上げることが難しい場合もあるため今回はこの方法を取らない.次期 OpenPNE バージョンなどで対応できるように検討することが適切であるとおもわれる.

基本的に本チケット(本バージョン)では後者の場合を取りうるが,この場合での検討事項は どのタイミングで 4byte 文字を切り落とすかとなる.
この問題はおそらく MySQL のバージョンや使用する文字コードなどが影響しているため Doctrine 以下で行いたいところが,データ格納時などに切り落とす処理を行うと影響範囲が大きく,Symfony で提供されているバリデーションがうまく働かなくなる可能性なども十分にありえるためあまり得策ではないといえる.

OpenPNE では似たような問題として ヌルバイト文字にも対応していて,これはリクエスト時にヌルバイトを切り落とすかどうかを指定して,切り落とした文字列を用いている.本チケットの問題も同様の対処により解決できるのではとかんがえられる.

つまりリクエストから文字列を取得してくる際に 4byte 文字を削除するような対策を取る.
この対策を施した OpenPNE では 4byte 文字を扱えないという仕様になり, 4byte 文字を削除した文字列を入力値として扱う.
こうすることで,空文字列が許容されないフォームに対しても 4byte 文字のみで構成された場合に空文字列として扱わせることで Symfony の Validator をそのまま用いることができる.

#9 Mutsumi Imamura11年以上前に更新

  • 説明 を更新 (diff)

#10 Yuya Watanabe11年以上前に更新

4byte 文字がすでに存在する場合があるかどうかを調査する必要がある.

#11 Yuya Watanabe11年以上前に更新

すでに投稿された 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 | 
+----+-----------+-------------------------+----------------------+-------------------+-------------+-------+-----------+--------+------------+---------------+------------+----------+--------------------------------------+---------------------+---------------------+

#12 Yuya Watanabe11年以上前に更新

  • 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 を用いることが考えられるが,本チケットの修正を対象とする修正としては少々大きいため,他の動作に影響がないか調査する必要がある.

#13 Mutsumi Imamura11年以上前に更新

  • 担当者Yuya Watanabe から Kousuke Ebihara に変更

#14 Kousuke Ebihara11年以上前に更新

  • ステータスNew(新規) から Pending Review(レビュー待ち) に変更
  • 進捗率0 から 50 に変更

#15 Kousuke Ebihara11年以上前に更新

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 を追加

#16 Rimpei Ogawa11年以上前に更新

  • ステータスPending Review(レビュー待ち) から Pending Testing(テスト待ち) に変更
  • 進捗率50 から 70 に変更

#18 isao sano約7年前に更新

  • ステータスPending Testing(テスト待ち) から Won't fix(対応せず) に変更
  • 進捗率70 から 0 に変更

OpenPNE 3.8.4 にて対応済みであったため、対応せずとします。

他の形式にエクスポート: Atom PDF