Project

General

Profile

Feature #14352

Array#pack("M") Quoted-Printable with binary mode

Added by kirika (Toshio Maki) 9 months ago. Updated 6 months ago.

Status:
Closed
Priority:
Normal
Assignee:
-
Target version:
-
[ruby-core:84835]

Description

英語で書こうと思ったのですが、うまく伝えられないかもしれないので日本語で書かせてください。すみません。

mail libraryというgemを使ってメールをパースしたり、書きだしたりしているのですが、
Quoted-Printableでエンコードされたバイナリがメールに含まれていた場合に、メールを破壊する
可能性があるということが分かりました。

調査してみると、RubyのArray#Pack("M")を使ったときに、以下のように
\rは=0Dに変換してくれるのですが、\nは=0Aではなく、そのまま\nで維持するのが問題のようで、
mail libraryはパース前の処理に\nを\r\nに変換するという処理を加えるのですが、その時に\nが
\r\nに変換され、オリジナルのバイナリを破壊してしまうことが分かりました。

puts ["あいうえお\r\nかきくけこ"].pack('M')

実行結果

=E3=81=82=E3=81=84=E3=81=86=E3=81=88=E3=81=8A=0D
=E3=81=8B=E3=81=8D=E3=81=8F=E3=81=91=E3=81=93=

Quoted-Printableにおいては、レイアウトのための改行は=\r\nにする、ということが決まっているので、
このように\nが単独で残るということはないような気がするのですが、昨年末にまつもとさんにご質問
させていただいた際にPerlの実装を参考にした、ということを伺ったので、Perlについても調べて
みたのですが、Perlは$binmodeというフラグで、バイナリ時の挙動と分けているようです。

http://perldoc.perl.org/MIME/QuotedPrint.html
(もしかすると、参考にした実装とは関係ないかもしれません)

今からpack('M')の挙動を変えると、影響範囲が大きそうなので、バイナリをQuoted-Printableでエンコード
するためのオプションがあれば、いいと思うのですが、packにどういうオプション名をつければよいか、
またpack.cのqpencode関数にbinmode用の拡張しても良いかを含めてご相談させてください。

出来れば今使っているrubyのバージョンは2.3とかだったりするので、2.3.x, 2.4.xにバックポート可能かどうかも
含めて相談させてもらえれば、と思います。


Related issues

Related to Ruby trunk - Bug #14741: [].pack('M') quoted printable RFC2045 contains only LF, does not contain CRLF, MessagesClosed

Associated revisions

Revision 3a5d1e4b
Added by naruse (Yui NARUSE) 6 months ago

pack/unpack M only handles LF line breaks [Feature #14352]

git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@63191 b2dd03c8-39d4-4d8f-98ff-823fe69b080e

Revision 63191
Added by naruse (Yui NARUSE) 6 months ago

pack/unpack M only handles LF line breaks [Feature #14352]

History

#1 [ruby-core:84842] Updated by mame (Yusuke Endoh) 9 months ago

遠藤と申します。ご報告ありがとうございます。

Quoted-Printable を規定しているのは RFC 2045 の "6.7. Quoted-Printable Content-Transfer-Encoding" だと思います。関連しそうなところを中心にルールを要約すると

(1) canonical form での CRLF 改行の CR と LF 以外の文字は、原則として =0C のようにエンコードする。ただし他のルールが別表現を許している場合はそれ以外でもよい。
(2) イコール以外の printable な文字(32〜60、62〜126)はそのままエンコード結果に出してもよい。
(3) 空白文字(9 と 32)もそのまま出してもよい。ただし行末に置く場合は =20 みたいにしないとダメ。
(4) テキスト内の CRLF 改行は、出力でも CRLF にしないといけない。メディア形式によっては CRLF 改行表現がないこともあるので、そういう形式ではハード改行を含めてはダメ。非テキストのエンコードでは =0D や =0A や =0D=0A が普通に現れる。
(5) 一行は 76 文字まで。行末に = をつけること。

と書いてあるように読めました(規格の誤読に定評と実績があるので、他の人にも確認してほしいです)。
この解釈が正しければ、現在の挙動である "...=0D\n..." は明確に否定されていると思います。

今からpack('M')の挙動を変えると、影響範囲が大きそうなので、バイナリをQuoted-Printableでエンコード
するためのオプションがあれば、いいと思うのですが、packにどういうオプション名をつければよいか、

互換性は大切ですが、規格違反だとしたら(少なくともいつかは)直すべきであると思います。
また、テキストか非テキストかでエンコード結果を変えることは、規格によって事実上要求されているように読めました。

思いつきですが、できればオプションを増やしたくはないので、エンコード対象の文字列の文字コードが ASCII-8BIT かそれ以外かで切り替えるのはどうでしょうか。> 成瀬さん

#2 [ruby-core:84843] Updated by usa (Usaku NAKAMURA) 9 months ago

遠藤さんのRFCの解釈内容に同意します。

直さなければいけないのも同意しますが、互換性はやっぱり大切、というか、
テキストかバイナリかは本来必ず指定されなければいけない(判断のしようがない)と
思いますので、やはりここはオプションを追加して、

M : 従来挙動。ただし将来削除するものとして警告を出す
M0: RFC2045テキスト準拠
M1: RFC2045バイナリ準拠

というのを提案します。
01 は安直にbinmode指定フラグとみなして言ってますが、ちょっと
暗号的に過ぎるのかなあ、という懸念はあります。
(もっとも、packテンプレート自体が元々暗号的ですが)

#3 [ruby-core:84844] Updated by matz (Yukihiro Matsumoto) 9 months ago

うささんの案には惹かれるものがありますが、 M を削除するよりは将来の挙動を変更すると警告するほうが良いと思います。で、デフォルトでどちらかによせるかというとバイナリかな?

Matz.

#4 [ruby-core:84846] Updated by naruse (Yui NARUSE) 9 months ago

まず報告の\rと\nが逆なように思います。

で、検討にあたって互換性というかまず現状を考えたいのです。
そもそも quoted-printable は改行がCRLFであるべきところ、pack("M")の出力はLFだけを通すんですよね。

という挙動だけ見るとこれはバイナリモードなのかと最初考えますが、
LFのままではネットワークに流せませんからこの出力をメールなどで出す場合は s/\n/\r\n/g すればよい……
とみせかけてそうすると今度は物理改行がLFだった情報が無くなるので、そもそもLFもエンコードして
すべてソフト改行にしないと元の情報が残りません。
これは現状のpack("M")でやるより自分でgsubした方が早いですし、そもそもBASE64使った方がいいですね。

現状は基本的にはテキストモードであって、後処理の便宜のためにLFになっているだけであり、
ヘッダとつなげるなりした後 s/\n/\r\n/g すればよいのだと仮定すれば、そこそこ使えるものだと考えられます。

すると、この場合 =0D\n が出力結果に含まれるのは明らかに意図せぬものとなります。
というようなことを考えると、入力のCRLF(や単独のCR)は誰かがLFに正規化するべきなのでしょう。

べき論としては出力をCRLFに変えるべきなのかもしれませんが、
トラップとしては大きすぎるので自分で気付くだろうからまぁそのままでいいかな。

RFC2045バイナリ準拠だとCR、LF、CRLF全てエンコードになると思うのですが、それって必要なのですかね。
それが必要な場合すでにgsubを使って独自実装しているような気がします。

#5 [ruby-core:84848] Updated by kirika (Toshio Maki) 9 months ago

  • Description updated (diff)

2018/01/13 10:53追記 \rと\nの表記が逆になっていたため訂正しました。

#6 [ruby-core:84849] Updated by kirika (Toshio Maki) 9 months ago

皆様ご意見いただきありがとうございます。

成瀬さんのおっしゃる通り、私の手元では現状はpack("M")の出力にgsubを付加して、呼び出し側で回避するコードを入れています。

また、Mailライブラリ側でも同種の議論があり、バイナリの場合はto_crlfで改変しないようにするのか、
pack("M")呼び出し時に、後処理をするのかなど、試行錯誤していますが、まだ落ち着いていない状況です。

https://github.com/mikel/mail/issues/1010
https://github.com/mikel/mail/pull/1113

Ruby側でバイナリモード時のQuoted-Printableがサポートされれば、それを呼び出すのが一番いいのではないかと思っているのですが、
pack("M")の出力に加工を加える、でも対応はできると思うので、ここでの方針が決まれば、mailライブラリのコミュニティにも報告しようかと思っています。

#7 Updated by naruse (Yui NARUSE) 6 months ago

  • Status changed from Open to Closed

Applied in changeset trunk|r63191.


pack/unpack M only handles LF line breaks [Feature #14352]

#8 [ruby-core:86601] Updated by naruse (Yui NARUSE) 6 months ago

議論したのですが、結論としてドキュメントイシューということになりました。

大本のPerlもそうですが、pack/unpackは前提として、Unix環境で入力をとり、LFのまま出力し、
その出力をsendmail等に流して、それらがLFをCRLFに変換することを想定したものであろうという見解に至りました。
よって、現状の挙動はその用途においては妥当なものなので、変更せず、ドキュメントの追記のみを行いました。

kirika (Toshio Maki) wrote:

また、Mailライブラリ側でも同種の議論があり、バイナリの場合はto_crlfで改変しないようにするのか、
pack("M")呼び出し時に、後処理をするのかなど、試行錯誤していますが、まだ落ち着いていない状況です。

https://github.com/mikel/mail/issues/1010
https://github.com/mikel/mail/pull/1113

Ruby側でバイナリモード時のQuoted-Printableがサポートされれば、それを呼び出すのが一番いいのではないかと思っているのですが、
pack("M")の出力に加工を加える、でも対応はできると思うので、ここでの方針が決まれば、mailライブラリのコミュニティにも報告しようかと思っています。

バイナリモード時の Quoted-Printable はハード改行をどう扱うかが一般にはよくわからないので、
汎用ツールであるRubyでは扱わないことになりました。
ad-hocな対応はmailライブラリの側で対応をお願いします。

#9 [ruby-core:86623] Updated by kirika (Toshio Maki) 6 months ago

なるせさん

ご検討いただきありがとうございました。
mailライブラリとしてはこの結果をベースに修正案を出してみようと思います。

#10 Updated by shyouhei (Shyouhei Urabe) 5 months ago

  • Related to Bug #14741: [].pack('M') quoted printable RFC2045 contains only LF, does not contain CRLF, Messages added

Also available in: Atom PDF