質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

89.62%

utf8プラグマを付けると 受け取った内容の一部が文字化けます。

解決済

回答 7

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 2,442

motokix

score 11

CGIモジュールを利用して、フォームを作成しているのですが、入力画面からフォームの内容を受け取る際
utf8プラグマを付けると受け取った内容の一部が 文字化けます。

現象はフォームから受け取ったコードが、Laten-1の文字コードが表示されてしまいます
以下のようにパラメーターを取得しています。

ソースコードは抜粋ですが、

<input type="text" name="name" id="name" value="名前" size="30">
に対応するパラメーター
<input type="hidden" name="fm_disp_name" id="fm_disp_name" value="お名前">
<input type="hidden" name="fm_need_name" id="fm_need_name" value="1">


fm_disp_項目名 があるものには、項目名を
fm_need_項目名
を受け取る仕組みです。

フォームから入力した全角文字が文字化けしてしまいます。
結果表示すると
お名前??????:Valueお名前値
のようになってしまいます。

コード
#!/usr/bin/perl
use strict;
use Encode;
use Encode::Guess;
use Encode::EUCJPMS;
#use Jcode;
use CGI;
use utf8;
my $cgi = new CGI;
require 'mimew.pl';                                # jcode($subject)->mime_encode が動かないとき
#エラーメッセージをブラウザに表示
use CGI::Carp qw(fatalsToBrowser);
use Data::Dumper;
###################################
print "Content-Type: text/html; charset=utf-8\n\n";
# 項目名
my @item_names = ();
# 項目の日本語表示名
my %item_disp = ();
# 項目の値
my %item_value = ();
# 項目の必須有無
my %item_need = ();
# フォームパラメータ
my %fm_param = ();
my $cgi_url = $cgi->url();    # このCGIのURL

#######################################################################
# 入力文字コード判定
#######################################################################
my $input_value = '';
my $name;
my $value;
my @values;
foreach $name ($cgi->param) {
    if ($name !~ /^submit$/i) {    # 項目('submit'は除く)
        @values = $cgi->param($name);
        $value = join(',', @values);
        if($cgi->param('fm_command') eq 'confsend') {    # 確認フォームからのときデコード
            $value = pack_param($value);
        }
        $input_value .= $value;
    }
}
#エラーメッセージ配列
my @err_namelist = ();

#送信データがなければエラーとする
if (0 == length($input_value)) {
    push(@err_namelist, '呼び出しエラーです。管理者にお問い合わせ下さい。');
    print_input_error(@err_namelist);
    exit;
}

#$input_jcode = Jcode::getcode($input_value);
my $guess = guess_encoding($input_value, qw/euc-jp shiftjis 7bit-jis/);
my $guess_encode = ($guess->name eq 'euc-jp') ? 'eucJP-ms' : $guess->name;

#######################################################################
# パラメータ読み込み
#######################################################################

my @checkboxes_array;

foreach $name ($cgi->param) {
    if ($name =~ /^fm_/) {    # フォームパラメータ
        $value = $cgi->param($name);
        if($cgi->param('fm_command') eq 'confsend') {    # 確認フォームからのときデコード
            $value = pack_param($value);
        }
        #$value = jcode($value, $input_jcode)->sjis;
        $value = ($guess->name ne 'shiftjis') ? encode('utf8', decode($guess_encode, $value)) : $value;
        $fm_param{$name} = $value;
        if($name =~ /^fm_disp_(\w+)/) {    # 項目の日本語表示名
            $item_disp{$1} = $value;
            if(!defined $item_value{$1}) {
                push(@item_names, $1);
                $item_value{$1} = '';
            }
        } elsif($name =~ /^fm_need_(\w+)/) {    # 項目の入力必須
            $item_need{$1} = $value;
        }
    } elsif($name !~ /^submit$/i) {    # 項目('submit'は除く)
        if(!defined $item_value{$name}) {
            push(@item_names, $name);
        }
        @values = $cgi->param($name);

        my $len = @values;
        if ($len > 1) {
            logger($name . 'はチェックボックスなのです!で項目数は' . $len . 'なのです!(ただし複数選択可能なselectタグかもしれない・・・けど項目の上限を決めておけばいい($lenが上限以下ならselectでも項目をCSVに出す等))');
            for (my $k=0; $k<$len; $k++) {
                logger(@values[$k]);
            }
            #MEMO:
            #確認画面で項目数がここでわかったので、後ろの画面に$nameは$len項目ですと渡せばcsvでいい感じにできるのです!
            #実際の処理には現状確認画面でスペース?区切りか何かにされてしまっているため、項目数がうまくは取れないと思われる(確認画面のhiddenタグ参照の事)
            my $object = {
                name => $name,
                len => $len,
                #values => @values,
                values => [],
            };
            for (my $k=0; $k<$len; $k++) {
                logger(@values[$k]);
                push(@{$object->{values}}, @values[$k]);
            }
            push(@checkboxes_array, $object);
        }

        $value = join(',', @values);
        if($cgi->param('fm_command') eq 'confsend') {    # 確認フォームからのときデコード
            $value = pack_param($value);
        }
        #$item_value{$name} = jcode($value, $input_jcode)->sjis;
        $item_value{$name} = ($guess->name ne 'shiftjis') ? encode('utf8', decode($guess_encode, $value)) : $value;

        if($item_disp{$name} eq '') {
            $item_disp{$name} = $name;
        }
    }
}

my $len = @checkboxes_array;
if ($len > 0) {
    logger('新規オブジェクト配列は' . $len);
    for (my $i=0; $i<$len; $i++) {
        logger(@checkboxes_array[$i]->{name});
        logger(Dumper @checkboxes_array[$i]);
    }
}

    foreach (keys %fm_param) {
        print "\$fm_param\{$_\} = $fm_param{$_}<br>\n";
    }
    foreach (@item_names) {
        my $name = conv_html($item_disp{$_});
        my $value = conv_html($item_value{$_});
        print "$name$value ($item_need{$_})<br>\n";

    }
    exit;

### csvに書けるように変換
sub conv_csv {
    my ($str) = @_;
#    $str =~ s/</&lt;/g;
#    $str =~ s/>/&gt;/g;
    $str =~ s/\t//g;
    $str =~ s/\r//g;
    $str =~ s/\n//g;
    $str =~ s/\,/"\,\"/g;
    #$str =~ s/"/""/g;
    return '"' . $str . '"';
    #return '' . $str . '';
}
sub conv_tsv {
    my ($str) = @_;
#    $str =~ s/</&lt;/g;
#    $str =~ s/>/&gt;/g;
    $str =~ s/\t//g;
    $str =~ s/\r//g;
    $str =~ s/\n//g;
    $str =~ s/\,/"\t\"/g;
#    $str =~ s/"/""/g;
    return '"' . $str . '"';
    #return '' . $str . '';
}

### htmlで表示できるように変換
sub conv_html {
    my ($str) = @_;

    $str =~ s/</&lt;/g;
    $str =~ s/>/&gt;/g;
    $str =~ s/\t//g;
    $str =~ s/\r//g;
    $str =~ s/\,/&nbsp;/g;
    $str =~ s/\n/<br>\n/g;

    return $str;
}

### htmlで表示できるように変換
sub conv_html2 {
    my ($str) = @_;

    $str =~ s/</&lt;/g;
    $str =~ s/>/&gt;/g;
    $str =~ s/\t//g;
    $str =~ s/\r//g;
    #$str =~ s/\,/&nbsp;/g;
    $str =~ s/\n/<br>\n/g;

    return $str;
}
sub conv_mail {
    my ($str) = @_;
#    $str =~ s/</&lt;/g;
#    $str =~ s/>/&gt;/g;
    $str =~ s/\t//g;
    $str =~ s/\r//g;
    $str =~ s/\n//g;
    $str =~ s/\,/"\t\"/g;
#    $str =~ s/"/""/g;
    return '"' . $str . '"';
    #return '' . $str . '';
}

### hiddenパラメータに格納できるようにエンコード
sub unpack_param {
    my ($str) = @_;

    $str =~ s/([^\w.\/\-@ ])/'%'.unpack('H2',$1)/eg;

    return $str;
}

### エンコードしたhiddenパラメータをデコード
sub pack_param {
    my ($str) = @_;

    $str =~ s/%([A-Fa-f0-9]{2})/pack('H2', $1)/eg;

    return $str;
}

sub conv_ahtml {
    my ($str) = @_;
#    $str =~ s/</&lt;/g;
#    $str =~ s/>/&gt;/g;
    $str =~ s/\t//g;
    $str =~ s/\r//g;
    $str =~ s/\n/<br>\n/g;

    return $str;
}
  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • argius

    2015/12/16 13:28

    一部が文字化けということですが、どのような文字の場合に文字化けするのでしょうか。全角文字はすべて文字化けしますか?

    キャンセル

  • argius

    2015/12/16 14:28

    すみません、もう一点。送信フォームと確認用ページのHTMLは、charsetがutf-8になっていますか?

    キャンセル

  • motokix

    2015/12/16 14:34 編集

    はい、なっております。 確認画面は、perlのスクリプトから別のファイルを読みますが、そこはUTF-8のファイルです。

    キャンセル

  • argius

    2015/12/16 14:48

    承知しました。ありがとうございます。

    キャンセル

回答 7

+1

(追記)

最後の出力の直前に、utf8::decode($value)を行うことで、解消すると思います。

私もこの辺は詳しくないので、下記リンク先を参照してください。

一部引用:

use utf8 とは、 このCGIスクリプト内で扱う全角文字は文字コードがUTF-8であり、且つ、扱う全角文字データ全てにutf8フラグ(目印みたいなもの/以下単に「フラグ」と呼称)を付けてあるのでそのつもりで処理してね、とPerl側に教えてやる宣言のこと。

PerlのCGIのutf-8改造で文字化けしたときの処方箋
http://mycc.s33.xrea.com/data/pc/perl_use_utf8.html


(最初の回答)

残念ながら回答ではないのですが、(※回答を追記しました)
確かに再現はしましたので、ここにご報告します。

環境:

  • OS X 10.11.2
  • Apache 2.4.16
  • Perl 5.18.2

入力値には、環境依存文字でないとguess_encodingutf-8 or shiftjisと推測されてしまうので、環境依存文字を入力値に加えました。
(例:名前①)

解せないのは、ハッシュに値を設定した直後(116行目)では文字化けしていないのに、出力の箇所(139行目)では文字化けしてしまうという点です。conv_htmlを外しても文字化けします。

以上、他の回答者の方々の参考になれば。
私ももう少し調べてみます。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2015/12/17 19:14

    バグだとしたらお手上げですし、utf8プラグマによるトラブルはけっこうあるみたいです。
    他に回答や案がなければ、ダブルクオーテーションで囲まない方法で対応したほうが早そうです。

    お役にたてずに済みません。

    キャンセル

  • 2015/12/18 15:03

    htmlにあるinput要素のname属性を日本語化した際にマッチングさせることは可能でしょうか?

    キャンセル

  • 2015/12/18 15:09

    name属性を日本語にすることは経験がないのでどうなるか分かりません。
    今回の現象がどう影響するかも予想がつきませんね。

    キャンセル

checkベストアンサー

0

コードをutf8で書いた上で、入力したらutf8からdecode()、出力前にutf8にencode()です。
プログラム内で文字列として扱えるのはdecode()の戻り値と、コード内に直接書いた文字列ぐらいです。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

0

下記の 2点 を試してみてはいかがでしょうか?

(1) 2,3 行目あたりに下記の記述を挿入

   use utf8 ; 

(2) コードを保存するときの 文字コードを utf8 にする

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2015/12/16 12:15

    そのutf8プラグマを入れると、入力値が文字化けます。
    ソースコードはUTF-8です。

    キャンセル

0

わたしの環境(Perl v5.14.2 , Ubuntu 12.04.1 LTS)で、コンパイルエラーとなりました。 require はコメントアウトしました。 とりあえず気が付いたのは、42行目 for の ( に対する ) がない。71行目の行末に 逆引用符 ` がある点です。 実際に動かしてみると、挙動を追いかけやすくなるかと思いました。 公開可能な部分をペーストしていただけたのか、とは思います。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2015/12/16 15:16

    抜粋コードで確認できるようになりました。
    HTMLで以下のようなフォームを作成しCGIにsubmitしてください
    <form name="form1" method="post" action="test.cgi" id="form_id">
    <input type="text" name="name" id="name" value="名前" size="30">
    に対応するパラメーター
    <input type="hidden" name="fm_disp_name" id="fm_disp_name" value="お名前">
    <input type="hidden" name="fm_need_name" id="fm_need_name" value="1">
    </form>
    と fm_disp_項目名がfm_needは必須という風に分解されます。

    キャンセル

0

HTMLを UTF-8 で記述したケースでは、正常に動作しました。

HTML ここから
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html lang="ja">
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<title>テスト 20151216</title>

<body bgcolor="#ffffff">
<b></b><br>

<p>
<form name="main" action="../../cgi-bin/tera_test_20151216/tera_test_20151216.cgi" method="post">

<table cellpadding="5" cellspacing="0" border="1">
<tr>
  <td>
    <input type="text" name="name" id="name" value="名前" size="30">
  </td>
</tr>
</table>

  <input type="hidden" name="fm_disp_name" id="fm_disp_name" value="お名前">
  <input type="hidden" name="fm_need_name" id="fm_need_name" value="1">
  <input type="hidden" name="fm_command" id="fm_command" value="confsend">

<input type="submit" value="送信">
</form>
</body>
</html>

HTML ここまで

Perl ここから

!/usr/bin/perl

use strict;
use utf8 ;

use Encode;
use Encode::Guess;
use Encode::EUCJPMS;
use CGI;
my $cgi = new CGI;
use CGI::Carp qw(fatalsToBrowser);
use Data::Dumper;
my @checkboxes_array;

print 'Content-Type: text/html;' ;
print "\n\n" ;
print '<html>' ;
print '<meta charset="UTF-8">' ;
print '<body>' ;

open my $OD,">>./log.txt";
foreach my $name ($cgi->param) {
    if ($name =~ /^fm_/) {
        my $value = $cgi->param($name);
        print $name.'['.Encode::encode('utf8',Encode::decode(Encode::Guess->guess($value),$value)).']'.'<br>' ;
        print $OD $name.'['.Encode::encode('utf8',Encode::decode(Encode::Guess->guess($value),$value)).']'.'<br>' ;
    }
}
close $OD ;

print '</body>' ;
print '</html>' ;

Perl ここまで

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

0

HTML を Shift JIS で書いたケースはエラーになりました。
Perl は 同じコードです。
Encode::Guess->guess の 戻りが『No appropriate encodings found!』となります。

HTML ここから
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html lang="ja">
<head>
<meta http-equiv="content-type" content="text/html; charset=Shift-JIS">
<title>テスト 20151216</title>

<body bgcolor="#ffffff">
<b></b><br>

<p>
<form name="main" action="../../cgi-bin/tera_test_20151216/tera_test_20151216.cgi" method="post">

<table cellpadding="5" cellspacing="0" border="1">
<tr>
  <td>
    <input type="text" name="name" id="name" value="名前" size="30">
  </td>
</tr>
</table>

  <input type="hidden" name="fm_disp_name" id="fm_disp_name" value="お名前">
  <input type="hidden" name="fm_need_name" id="fm_need_name" value="1">
  <input type="hidden" name="fm_command" id="fm_command" value="confsend">

<input type="submit" value="送信">
</form>
</body>
</html>

HTML ここまで

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

0

CGI.pm を使う場合は、pack(デコード)、unpack(エンコード)はなくても大丈夫のように思えます。
わたしの使っている環境(Ubuntu、Perl 5.14 )と違うのかもしれません。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 89.62%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる