こんにちはゲストさん。会員登録(無料)して質問・回答してみよう!

解決済みの質問

LWPのPOSTでバイナリが壊れる

LWPを使って、multipart/form-dataでバイナリデータを含むリクエストをSSL経由でPOSTすると、そのバイナリデータの部分が壊れてしまいます。
これはバグなのでしょうか?いろいろ探してみても情報が見つかりません。Proxy周りに似たようなバグがあるなどという情報は見つけたのですが、どなたか何か御存じないでしょうか。

具体的には、たとえば簡単な例をあげます。

まずチェック用のCGIスクリプトを用意します。
---------------------------------------
#!/usr/bin/perl

use strict;
my $buffer;
binmode STDIN, ':raw';
read(STDIN, $buffer, $ENV{CONTENT_LENGTH});
open my $fh, '>:raw', './request_body.dat' or die($!);
print $fh $buffer;
close $fh;
print "Content-type: text/plain\n\nOK";
exit;
---------------------------------------
これをcheck.cgiというような名前で、非SSLとSSLの2つのサイトに設置しておきます。
「:raw」などほとんど意味がないかもしれませんが、ようは受け取ったリクエストボディをそのままファイルに保存するだけのものです。
次に以下のようなバイナリデータ(GIFファイル)をPOSTするスクリプトを作って実行します。
---------------------------------------
#!/usr/local/bin/perl

use strict;
use LWP::UserAgent;
use HTTP::Request::Common 'POST';
my $ua = LWP::UserAgent->new();
my $http_res = $ua->request(POST('https://host/check.cgi',
Content_Type => 'form-data',
Content => [
test_data => 'ABCDEFG',
bin_data => ['./test.gif'],
],
));
$http_res->is_success or die $http_res->message;
print "OK\n";
exit;
---------------------------------------
まったく同じであるはずの、非SSLとSSLの2つのrequest_body.datファイルをバイナリエディタで比較すると、bin_dataの部分が異なります。法則性もよく判らず、かなり大きく違うようです。
ダメモトでlibwww-perlの最新版5.822を、Crypt::SSLeayも0.57をインストールしてみましたが状況は変わらず。
リクエストヘッダは特に問題なく、ようするにリクエストボディにバイナリデータが含まれるとエンコードに失敗するのかなと推測しています。
バイナリではない部分や通常のPOSTは問題ないのでOpenSSLのバグとは考えられないと思います。
それに、バイナリをPOSTなんてレアケースとも思えないんですが、情報が見つからないのも不思議で…。
何か情報を、あるいは代替案をご存知でしたら、御教授お願いいたします。
この手のものはLWPに依存しすぎていたため、他のHTTPでPOSTするようなものをよく知らないもので…。

投稿日時 - 2009-01-12 23:17:22

QNo.4625735

困ってます

質問者が選んだベストアンサー

何度もすみません、そして誤ったことを書いてしまいました。

No.3で用いた「CGI」は、他テスト用のもので、tasekiさん作成のcheck.cgiではありませんでした。
(なぜ「Internal Server Error」を返したのかはまだ調べていませんが・・・)

tasekiさん作成のcheck.cgiに向けて、HTTP/HTTPS および use utf-8あり/なし でPOSTしてみたところ、CGI、POSTスクリプトともすべてOKとなりました。
そして「HTTPS で use utf-8あり」の場合のみ、request_body.datのMD5値が異なりました。
(なおrequest_body.datのサイズはいずれのケースでも同じでした)

以上訂正いたします。

投稿日時 - 2009-01-14 20:06:13

お礼

taknak08さん、本当にありがとうございます。
おかげ様で(根本的な解決ではありませんが)バグの所在など分かり疑問も晴れました。
Internal Server Errorそのものは問題ありません。壊れたデータをバイナリエディタなどで見れば解るのですが、boundaryも壊れるので、POSTデータの解析&復元自体が失敗します。その場合にInternal Server Errorを返すかどうかはそのスクリプト次第ですから、お使いになったテストスクリプトがそうだっただけで、私が書いたcheck.cgiは解析&復元をせずにそのままファイルに書き込むだけなので、つまり壊れていようが何だろうが内容には一切関知しないので常にOKを返します。

そんなわけで、結果として、やはりutf8フラグのついたデータをSSLエンコードするときに壊れた、という推測がビンゴ!だったようですね。
そしてさらに判ったことは、taknak08さんの環境でも発生したということは、すなわち少なくともCrypt::SSLeayは無関係だった可能性が高い、という点です。

おそらく、前にも書いたようにwgetなどは問題ないので、これはLWP周りだと思います。
ハンドラの受け渡しLWP::Protocol::httpsあたりでフラグを落としていないのが原因なのかな、と推測していますが、もうそのあたりはメンテナさんに任せるとして、暫定策としてPOSTする前に呼ぶ側でフラグを落とせば良さそうです。

この問題が起きる可能性の盲点として、多く使われているであろうWWW::MechanizeとかでHTML::Formにて日本語などのページを解析する際で、HTMLフォームをutf8つきで取得、そのままPOST、なんてやると、この問題が起きますね。
しかも、そんなのに遭遇した人は、原因にたどり着くまで長そうです…。

本当に助かりました。ありがとうございます。
一応もう少し情報を待ってみてから、このスレッドは閉じたいと思います。

(ちなみに、CPANにレポートしたのは…以下略)

投稿日時 - 2009-01-14 21:01:10

このQ&Aは役に立ちましたか?

0人が「このQ&Aが役に立った」と投票しています

回答(4)

ANo.3

こんにちは。
新しいスクリプトでPOSTしてみたところ、おもしろい結果になりました。

「use utf8;」ありでHTTPSリクエストをすると、
 $http_res->is_success or die $http_res->message;
の行で
 Internal Server Error at ./xxx.pl line xx.
になります。test.gifもHTTPSサイトへPOSTされていないようです。
その他の場合はすべて大丈夫でした。つまり「use utf8;」をコメントアウトして実行すると、HTTPSでもtest.gifが正常にPOSTできます。
またHTTPの場合は「use utf8;」があっても無くても問題なくPOSTできました。

「use utf8;」は鬼門ですね、本当に・・・Perlファンとしては、UTF-8処理ごとき(?!)で変にコケられてしまうのは悲しいかぎりです。。。
解答が分かってしまったところでググってみれば、やはりお仲間がいるようですね。
(「Perl LWP::UserAgent HTTPS utf8 POST」でググってみました)
http://markmail.org/message/varrwabxflwsh4vo#query:Perl%20LWP%3A%3AUserAgent%20HTTPS%20utf8%20POST+page:1+mid:tzotme5w5h5tz5yy+state:results

それでは、世のPerlファンのためにバグ報告を!っと思ったら、早速CPANにレポートされているご様子。すばらしい!
私も勉強になりました。ありがとうございました。

投稿日時 - 2009-01-14 19:53:13

ANo.2

最初の者です。
以下、POSTスクリプトを動かした環境をお伝えします。

$ rpm -qa | grep -i libwww
perl-libwww-perl-5.805-1.1.1

$ perl -MCrypt::SSLeay -le 'print $MCrypt::SSLeay::VERSION'
Can't locate Crypt/SSLeay.pm in @INC ...
$ perl -MNet::SSLeay -le 'print $Net::SSLeay::VERSION'
1.30
$ perl -MIO::Socket::SSL -le 'print $IO::Socket::SSL::VERSION'
1.01

tasekiさんの現象ですが、私にも原因はよく分かりません。うーん、何なんでしょうねぇ。。。
ちなみに私の環境では、上記のとおりCrypt::SSLeayというパッケージは用いられていないようです。このあたり何かヒントがあるかもしれません。
ではがんばってください。ご検討をお祈りしています。。。

投稿日時 - 2009-01-14 11:31:24

補足

何度もありがとうございます。
確かlibwwwの過去バージョンはCrypt::SSLeayが見つからない場合は代替モジュールを探す仕様だったので、おっしゃるとおりtaknak08さんの環境ではCrypt::SSLeayが用いられていないようですね。

それも含めていろいろ検証を続け、おかげ様で、だいぶ判ってきました。

まずは、関係ないかもしれませんが、Crypt::SSLeayのドキュメントを見ると、「これを使えばSSLでGET, HEAD, POST ができます。POSTについて詳しくはLWPを見てください」と、どういうわけかわざわざPOSTだけ特別扱い(?)しているのは、何か懸念事項があるから、という気もするような、しないような…。

そして、条件が絞り込まれていくうち、UTF-8が浮かび上がってきました。
バイナリデータの中身によって問題が起きたり起きなかったりするのが、どうしても不思議だったんですが、ついに見つけ出しました…、ほぼ確実に問題が起きる条件を。
taknak08さんには何度も申し訳ないのですが、本当にもしお時間があったらで良いので、以下を試していただけないでしょうか。
POSTスクリプトの改訂版です。
---------------------------------------
#!/usr/local/bin/perl

use strict;
use utf8;
use LWP::UserAgent;
use HTTP::Request::Common 'POST';
my $ua = LWP::UserAgent->new();
my $http_res = $ua->request(POST('​https://host/check.cgi',​
Content_Type => 'form-data',
Content => [
test_data => 'ABCDEFG',
bin_data => ['./test.gif'],
],
Head1 => 'A',
));
$http_res->is_success or die $http_res->message;
print "OK\n";
exit;
---------------------------------------
2行追加しただけです。
もし変化が無いようでしたら、test.gifを別のファイルにしてみていただければと思います。

もしこれで問題が起きる、つまりやはりUTF-8が絡んでいたのなら、少し解るところもあります。
LWP::UserAgentのソースを読んでみたんですが、requestメソッドの中で、受け取ったRequestオブジェクトに対して追加のヘッダがあれば、内部で処理して追加しているんですね。
それが上記では「Head1」ですが、ここにutf8フラグが立っていると、SSLの処理で問題が起きる、と言うことかもしれません。
そして、「SSLの処理」というのは、上記スクリプトでtaknak08さんの環境では問題が起きないなら、Crypt::SSLeayだけの問題かもしれません。
といっても、そもそもASCIIしか使っていないのでUTF-8なんて関係ない気もするんですけどね…。

上記の場合ですが、とりあえずの解決策も判りました。
判りましたというか…、単にutf8フラグを落とせば良いだけで、試してみたら正常にPOSTできました。
しかし推測が当たっていて、かつ上記のような条件でPOSTする場合、とケースが限られますが。

投稿日時 - 2009-01-14 16:00:56

ANo.1

tasekiさんが作成された、チェック用CGIおよびPOSTスクリプトを
手元のhttpおよびhttpsのサーバで動かしてみたのですが、
生成されたファイル(request_body.dat)には差異はありませんでした。

POSTスクリプトを動かした環境は以下のとおりです。
 ・CentOS 5.2 x86_64
 ・$ rpm -qa | grep -i ssl | sort | uniq の結果:
   openssl-0.9.8b-10.el5
   openssl-devel-0.9.8b-10.el5
   perl-IO-Socket-SSL-1.01-1.fc6
   perl-Net-SSLeay-1.30-4.fc6
なおチェック用CGIはレンタルサーバで動かしたため、
パッケージなどの詳しい情報はわかりませんでした。
またチェック用CGIを上記サーバへ置き、HTTPでPOSTしてみましたが、
結果は同じでした。(=レンタルサーバへのHTTPSと差異はありませんでした)

回答にはなっていませんが、ご参考まで・・・。

投稿日時 - 2009-01-13 17:36:48

お礼

有用な情報ありがとうございます。
おそらくPOST側の問題なのでしょうね。
ひとつ判ったのが、極端に小さいデータ、たとえば数ピクセルの画像などの場合は問題ないことと、私のほうでもレンタルサーバーやレンタルブログへの画像投稿など試してみましたが、相手サーバー=POST先に関係なく問題が起きる、あるいはデータによってはPOST先に関係なく起きないので、やはりPOSTする側の問題だと思います。
POSTする側を複数で試していますが、wgetなどは正常なので、やはりOpenSSLなどではなくLWPまわりの何かだと推測しています。

もしよろしければ、libwww-perl、Crypt::SSLeayなどのバージョンもお教えいただけますでしょうか。

また、他に何か情報ありましたら、引き続きお待ちいたします。

投稿日時 - 2009-01-13 18:21:11

あなたにオススメの質問