CGI.pm は HTML5 に対応していないなど、ここ十数年保守されていません。
PerlでCGIスクリプトを書く場合はCGIモジュールを使うことが一般的です。まずは簡単なサンプルから。
[https://paiza.io/projects/UZvZ6FVdSRzwbDpU_qmMJw?language=perl 例]:
use v5.30.0;
use warnings;
use CGI;
my $title = "Simple Sample";
my $q = CGI->new;
print $q->header(-type=>'text/html', -charset=>'utf-8');
say CGI::html
  CGI::head(CGI::title $title),
  CGI::body(CGI::h1($title), CGI::p "Hello world!");
実行結果:
Content-Type: text/html; charset=utf-8
 
Simple Sample Simple Sample Hello world!
CGIモジュールのheaderメソッドはHTTPレスポンスヘッダを生成するメソッドです。「-type」でContent-Typeヘッダフィールドの値を指定します。また、「-charset」に文字符号化方式を指定すれば、Content-Typeヘッダフィールドの値にcharsetを付与することができます。
クエリ文字列を解析しパラメータを取り出すには、paramメソッドを使う。
use CGI;
my $q = CGI->new;
my $title = $q->param('title'); # ?title=Perl/CGI ならば "Perl/CGI" を返す
paramメソッドにはパラメータ名を渡す。引数なしで呼び出すと、すべてのパラメータ名と値のペアをリストとして返す。
use CGI;
my $q = CGI->new;
my %param = $q->param();
print $param{title};
このように、CGIモジュールを使うことで、CGIスクリプトに必要な処理を簡略化して記述することができます。
CGIスクリプトでエラーが発生した場合、サーバの設定によってはエラーログにエラーの内容が記載されるが、CGI::Carpモジュールを使うとウェブページ上にエラーメッセージを出力することができます。
use CGI::Carp qw(fatalsToBrowser);
fatalsToBrowserをインポートすると、致命的エラーが発生した場合にエラーメッセージを出力します。これにより、CGIスクリプトのデバッグが容易になります。
warningsToBrowserを呼び出すと、致命的でない警告メッセージをHTMLのコメントとして出力することができます。
use warnings;
use CGI::Carp qw(fatalsToBrowser warningsToBrowser);
warningsToBrowser(1);
CGI::Carpはdieやwarnをラップし、それらが呼び出されたときにエラーメッセージをHTMLとして出力する。モジュール内ではCarp、CGIスクリプト内ではCGI::Carpを使うことが推奨される場合があります。
CGIスクリプトでは外部からデータを渡されることが多いが、それらのデータをチェックせずに出力するなどしてクロスサイトスクリプティングなどの脆弱性を生む危険性があります。
perlに-Tスイッチを付けると、taintモードが有効となり、外部から渡された安全性が疑わしいデータを「汚染」されているものと見なす。汚染されたデータを加工せずに出力しようとすると、例外を発生させてスクリプトの動作を中断します。
use CGI;
my $q = CGI->new;
my $text = $q->param('text'); # $textは汚染されている
my $copy = $text; # $copyは汚染されている
$copy =~ s/&/&/g; # $copyは浄化された
print $copy; # OK
-Tスイッチは脆弱性を完全に防げるものではありません。上記のコードでは、例えばMIMEタイプがtext/htmlの場合、<や>などのHTMLの構文に使われる文字をエスケープする処理を$copyに施していないため、任意の構文を埋め込むことが可能になってしまいます。
このように万全ではないものの、汚染されたデータの使用を抑制することはできるため、外部からデータを受け取るCGIスクリプトでは常に-Tスイッチを有効にすることが推奨されます。