CGI

Perl で、ブラウザから送られてきたデータを CGI.pm を使わずに処理する方法

最も原始的な例は以下のようになるだろう。今さらだけど、知っておいて損はないかもしれない。

HTTP プロトコルの場合、クライアントからサーバにデータを送る方法は POST メソッドか GET メソッドと仕様で定義されているから、POST の場合と GET の場合の2つのパターンを考えてデータを受け取ってあげればよい。

続きを読む

Perl で、画像のサムネイルを作成してブラウザから表示させる方法

以下のサンプルファイルを image.cgi と名前を付けて保存、アクセス権を chmod +x image.cgi とかして ブラウザから https://yourname.com/image.cgi とかにアクセスすればよい。 注意点としては、このスクリプトは画像しか表示できないことである。これと一緒に HTML ファイルを表示しようと思っても、HTTP プロトコルの仕様上不可能である。なぜかというと HTTP プロトコルを通して画像を表示する場合は、すでにこのスクリプトが HTTP ヘッダとして Content-type: image/jpeg を最初に出力してブラウザが受け取っているからで、このスクリプトの中で同時に(画像を表示した後に) HTML 文書を表示させようとて Content-type: text/html を出力(print "Content-type: text/html\n\n)してもブラウザは受け付けてくれないのである。 #!/usr/bin/perl use GD;
続きを読む

Requirements for Mobile Phones (English)

I would like to propose the unique requirements for mobile phones. The following items are significant considerations under mobile site development, especially in i-mode phones.

  1. UserAgent type that is, a Browser Name of CGI environments which is sent by a mobile phone to a web server.
  2. Range of IP addresses in each mobile operator, which is required to judge whether a user is using a mobile phone or a PC in order to support both of them in the web site. In Japanese mobile phone operators, they are using their specific proxy servers, which are accompanying with the specific range of IP addresses.
  3. Screen Size of each mobile phone, especially the size of width, which would be found in a CGI environment UserAgent in the latest phones
  4. Image Format such as JPEG, PNG and GIF supported by each mobile phone
  5. Maximum Size of Receiving Bytes per a page
  6. Difference of Handling Colors in each mobile phone
  7. Difference of Smilies (Picture Icons) both in each mobile phone and operator
  8. Name Convention of an E-mail Address in each operator (Japanese i-mode e-mail system violates RFC).
  9. Available Character Set in each operator (Shift-JIS, UTF-8 in Japan)
  10. Available Character Types in each operator (such as ZENKAKU, HANKAKU in Japan)
  11. Available Input Method in each operator (Access-key concept in CHTML)
  12. Support of Native Functions of a mobile phone (i.e. switching a browser screen to an e-mail application with 'mailto:' in CHTML at a single screen of a mobile phone, launching Java application, and directly calling to someone with 'tel:' in CHTML, etc)
  13. Difference in between Official Sites and Unofficial Sites (The acquiring information is different)
  14. Session Management because we cannot use cookie in i-mode phones
続きを読む

Perl で、JavaScript と連動してサーバにアクセスログを残す方法

ウェブページへのアクセスログを記録する方法は、いろいろな方法があると思う。もっとも簡単なのは Apache などのウェブサーバが自動的に記録するログをチェックすることで、これには何の手間もいらない。しかし、レンタルサーバなどでログが自由に扱えない場合はどうしたらいいのだろう?

そのひとつが、画像としてリンクされている Perl などの CGI を呼び出す方法だ。利用者に知られずこっそりとアクセスログを取るには 1x1 ピクセルの画像をそのウェブページの背景と共に表示してしまえば、そのウェブページのソースをチェックされない限り、ほとんどの場合は気づかれずにすむだろう。

そんなわけで、以下のように JavaScript と対応する Perl スクリプトで、アクセスログを記録する仕組みを作ってみた。ログの形式は Apache に準拠する(たぶん)。

続きを読む

PHP で、連続カウント防止・カンマ区切り付きカウンタを作成する

PHP で、以下の仕様を満たすカウンタを作成してみる (このサイトで使っています)。

  1. 同一 IP からの連続したアクセスをカウントしない (連続カウント防止)。
  2. カウンタの数字をカンマ (,) で区切って表示する。

連続カウント防止機能は、

if(($ip_check == 1 && $ip != $_SERVER['REMOTE_ADDR']) ...

の部分でチェックしている。$ip には、「直前にアクセスしてきた IP アドレス」が入っている。同じ IP アドレスからアクセスし続ける限りはカウントされない。通常はこれでわりと正確なカウンタとなり得ると思うが、ファイアウォールの内側にいる同一マシンなどで、ファイアウォールのアドレスがアクセスのたびに変わる場合はこの限りではない。また、ケータイからのアクセスなどの場合も毎回アクセス元の IP アドレスがある一定の範囲内で変わるので連続カウント防止にはならないことに注意。

カウンタの数字をカンマ区切りにしているのは以下の 1行である。

$total = number_format($total); // カンマ区切りにする

以下は PHP の全ソース。

<?php // count.php // (c) 2006 twinkle.cc // https://perltips.twinkle.cc/ // updated by yas 2006/01/03 // updated by yas 2003/05/07 // updated by yas 2003/03/05 // オリジナル: https://works.xrea.jp/ // ※このスクリプトと同じディレクトリに「count.dat」という名前のファイルを作成し、置いておくこと。 if(!$file) { $file='count.dat'; } // 設定 ここから ──────────────────────────── $HOME = 'count.datがあるディレクトリのパス'; // ディレクトリ $data = "$HOME/$file"; // カウント記録ファイル $ip_check = 1; // 連続カウント防止(yes=1 no=0) // 設定 ここまで ──────────────────────────── $count = file($data); // ファイルを配列に // データ内の文字列を分解してそれぞれの変数に代入 list($total, $ip)=explode('<>', $count[0]); // カウントアップ処理 if(($ip_check == 1 && $ip != $_SERVER['REMOTE_ADDR']) || $ip_check == 0) { <pre>$total++; $new_data = implode('<>', array($total, $_SERVER['REMOTE_ADDR'])); $fp = fopen($data, 'w'); // 書きモードでオープン flock($fp, LOCK_EX); // ファイルロック fputs($fp, $new_data); // 書き込み flock($fp, LOCK_UN); // ロック解除 fclose($fp); // ファイルを閉じる } $total = number_format($total); // カンマ区切りにする print $total; // カウンタを表示 ?>

このソースを自身の PHP のカウンタを表示したいページを同じディレクトリに置き、同じディレクトリに count.dat という名前のファイルを置く (私のところは「chmod 644 count.dat」で動いているがお使いの環境によってはパーミッションに注意)。

最後に、カウンタを表示したい場所に以下のように書けばよい。

<?php include('count.php'); ?>
続きを読む

Perl で、Excel のデータ (XML) をアップロードして処理する方法

  1. まず、Excel で 以下のように category1、category2、name、address、phone、link、comment1、comment2 というカラム構成のワークシートを作成し、適当にデータを入力、XML 形式で保存しておく。
    注: 住所は米国の住所でないと正しく処理されないことに注意
  2. 次に、以下にあるように gmap_excel.html と gmap_excel.cgi の 2つのファイルを用意する (.gmap_excel.cgi 中のパスの設定などは適宜変更のこと)。
  3. gmap_excel.html から上の XML ファイルをサーバにアップロードする。
  4. サーバ側では Excel の XML データを処理して、gmap_excel.xml というファイルが出力される。
  5. このサーバ側で出力された gmap_excel.xml ファイルは Google Maps API を使った AJAX クライアントから利用できる。

gmap_excel.html のファイルの内容

<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <link rel="stylesheet" href="styles-site.css" type="text/css" />
</head>
<body>
    <form enctype="multipart/form-data" action="gmap_excel.cgi" method="post">
        <input type="hidden" name="MAX_FILE_SIZE" value="2000000" />
        <table width="600">
            <tr><td>Names file:</td><td><input type="file" name="file" />
                    <input type="submit" value="Upload" /></td></tr>
        </table>
    </form>
</body>

gmap_excel.cgi のファイルの内容

#!/usr/bin/perl use lib "path_to_your_perl_lib/lib/perl5/site_perl"; use strict; use Geo::Google; use CGI; our $XML_HEADER = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"; # XML ヘッダ our $HOME_DIR = 'your_home_dir'; our $LOCK_DIR = 'path_to_lock_dir/lock'; # do chmod 777 path_to_lock_dir/lock our $LOCK_FILE = 'lock'; our $XML_FILE = 'path_to_your_output_xml/gmap_excel.xml'; # 出力用 XML ファイル名 our $col_index = 0; # Excel シートの現在の列(カラム)インデックス our $row_index = 0; # Excel シートの現在の行インデックス our @data = (); # 出力用データ our @current_data = (); # 現在のデータ # オブジェクト初期化 our $CGI = new CGI; our $GEO = new Geo::Google; # HTML 出力 print qq|Content-type: text/html\n\n|; print qq||; print qq||; print qq|

|; init(); print qq|

|; # 個々のデータのハンドラ sub dataHandler {
my ($expat, $text) = @_;

#  $row_index = 0 と 1 は、Excel で データのヘッダのため必要なし
if($text !~ /^\s+$/ && $row_index > 1) {    #  空白行でなければ
    $current_data[$col_index] = $text;
}
} # 要素の開始 sub startElement {
my ($expat, $tag, %attr) = @_;

if($tag eq 'Row') {            #  <Row>で始まるとき

    $col_index = 0;                #  カラムを初期化
    $row_index++;                #  行を進める
    @current_data = ();            #  現在の行データを初期化
}

if($tag eq 'Data') {            #  <Data>で始まるとき

    $col_index++;                #  列を進める
}
} # 要素の終了 sub endElement {
my ($expat, $tag) = @_;
my ($xml, $html);
my ($lng, $lat, $name, $link, $category1, $category2);
my ($street, $city, $state, $phone, $comment1, $comment2);

#  </Row> のとき
if($tag eq 'Row') {

    push(@data, {    # データに現在の行データを代入

        'category1' => $current_data[1],
        'category2' => $current_data[2],
        'name'      => $current_data[3],
        'address'   => $current_data[4],
        'phone'     => $current_data[5],
        'link'      => $current_data[6],
        'comment1'  => $current_data[7],
        'comment2'  => $current_data[8]
    });
}

#  </Workbook> のとき
if($tag eq 'Workbook') {

    $row_index = -1; #  行数を初期化 ($row_index = 0 はヘッダのため -1 からスタート)
    foreach my $row (@data) {

        $row_index++;

        if($row->{address}) {
            $lng = '';  $lat = '';
            my ($degree) = getDegrees($row->{address});
            if($degree) {
                $lng    = $degree->longitude;
                $lat    = $degree->latitude;
            }
        }

        $name      = $row->{name};
        $link      = $row->{link}
                   =~ /http:\/\/[!#-9A-~]+\.+[a-z0-9]/ ? $row->{link} : '';
        $category1 = $row->{category1};
        $category2 = $row->{category2};
        ($street, $city, $state) = split(',', $row->{address});
        $street    = $street;
        $city      = $city;
        $state     = $state;
        $phone     = $row->{phone};
        $comment1  = $row->{comment1} eq '-' ? '' : $row->{comment1};
        $comment2  = $row->{comment2};

        #  lng と lat が検索できたら (データが入っているということだから)
        if($lng && $lat) {

            $xml    .= "\t<marker\n"
                    .  "\t\tlng       = \"$lng\" lat = \"$lat\"\n"
                    .  "\t\tname      = \"" . $name      . "\"\n"
                    .  "\t\tlink      = \"" . $link      . "\"\n"
                    .  "\t\tcategory1 = \"" . $category1 . "\"\n"
                    .  "\t\tcategory2 = \"" . $category2 . "\"\n"
                    .  "\t\taddress1  = \"$street\"\n"
                    .  "\t\taddress2  = \"$city, $state\"\n"
                    .  "\t\tphone     = \"$phone\"\n"
                    .  "\t\tcomment1  = \"$comment1\"\n"
                    .  "\t\tcomment2  = \"$comment2\"\n"
                    .  "\t/>\n\n";

        } elsif($row_index) {

            $html   .= '<tr><td>'
                    .  "$row_index / $name / $street, $city, $state"
                    .  '</td></tr>';
        }
    }

    $xml    =  $XML_HEADER
            .  "<markers>\n"
            .  $xml
            .  "</markers>\n";

    if($html) {    #  エラーがあれば
        $html    = '<table>'
                 . '<tr><td>***** 以下、緯度経度が検索不能でした。</td></tr>'
                 . $html
                 . '</table>';
    } else {    #  エラーがなければ
        print '<br />done.';
    }

    #  ファイルのオープン
    &file_lock;        #  ファイルロック
    open(XML, ">$HOME_DIR$XML_FILE")
    or die('Cannot open an XML file: ' . "$HOME_DIR$XML_FILE");
    print XML $xml;    #  書き込み
    close(XML);        #  ファイルを閉じる
    &file_unlock;      #  ロック解除

    print $html;
}
} sub getDegrees { # 住所から緯度経度を検索
return $GEO->location( address => shift); # shift = address / リファレンスを返す
} sub init() {
# XMLパーサー作成 ('UTF-8')
my $xml_parser = new XML::Parser(   ProtocolEncoding => 'UTF-8',
                                    Handlers=>{ Start=>\&startElement,
                                                End  =>\&endElement,
                                                Char =>\&dataHandler });
# ファイルのオープンはいらない。input タグで指定した name 属性がそのままファイルハンドルになる
my $xml = $CGI->param('file');

# XMLパース処理
$xml_parser->parse($xml) or    die "XML error: $xml_parser";

# ファイルクローズ
close($xml);
} sub file_lock {
my $wait = 5;
while (!symlink('.',"$HOME_DIR$LOCK_DIR/.$LOCK_FILE")) {
    if (--$wait <= 0) {
# &error( '現在他の方が使用中です。', # 'しばらくしてから再度ご利用ください。');
    }
    sleep (1);
}
} sub file_unlock {
unlink ("$HOME_DIR$LOCK_DIR/.$LOCK_FILE");
} 1;

gmap_excel.xml 出力例

<?xml version="1.0" encoding="UTF-8"?>
<marker
    lng       = "-122.125690" lat = "37.413570"
    name      = "Tofu House"
    link      = ""
    category1 = "Restaurant"
    category2 = "Korean"
    address1  = "4127 El Camino Real"
    address2  = " Palo Alto,  CA 94306"
    phone     = "650-424-8805"
    comment1  = ""
    comment2  = "Santa Clara の Tofu House と同じ店。"
/>
...
続きを読む

Perl で、Shift JIS でスクリプトを保存するには?

Perl でケータイ向けのサイトを書いているとき、シフトJIS でスクリプトを保存しないければならない場合がある (文字列テーブルなど)。このとき、


Unrecognized character \x81 at ./your.cgi line XX.

といったエラーに悩まされているなら、

my $group = 'グル&#x30fc;プ';

などのように、「―」を「&#x30fc;」で表すようにするとよい (Mac の場合は、「ー」が「―」になるようで、この現象が起こる)。または、文字化けする文字の後ろに「\」を付ける。

以下は、Shift JIS で文字化けする文字の一覧。文字化けを起こしやすいよく使う文字は強調しておいた。

文字 - Shift-JIS - Unicode での記述方法


<strong>―</strong>  81 5C   &#x2015;<br />
<strong>ソ</strong>  83 5C   &#x30BD;<br />
Ы  84 5C   &#x042B;<br />
Ⅸ  87 5C   &#x2168;<br />
噂  89 5C   &#x5642;<br />
浬  8A 5C   &#x6D6C;<br />
欺  8B 5C   &#x6B3A;<br />
圭  8C 5C   &#x572D;<br />
<strong>構</strong>  8D 5C   &#x69CB;<br />
蚕  8E 5C   &#x8695;<br />
<strong>十</strong>  8F 5C   &#x5341;<br />
<strong>申</strong>  90 5C   &#x7533;<br />
曾  91 5C   &#x66FE;<br />
箪  92 5C   &#x7BAA;<br />
<strong>貼</strong>  93 5C   &#x8CBC;<br />
<strong>能</strong>  94 5C   &#x80FD;<br />
<strong>表</strong>  95 5C   &#x8868;<br />
暴  96 5C   &#x66B4;<br />
<strong>予</strong>  97 5C   &#x4E88;<br />
禄  98 5C   &#x7984;<br />
兔  99 5C   &#x5154;<br />
喀  9A 5C   &#x5580;<br />
媾  9B 5C   &#x5ABE;<br />
彌  9C 5C   &#x5F4C;<br />
拿  9D 5C   &#x62FF;<br />
杤  9E 5C   &#x6764;<br />
歃  9F 5C   &#x6B43;<br />
濬  E0 5C   &#x6FEC;<br />
畚  E1 5C   &#x755A;<br />
秉  E2 5C   &#x79C9;<br />
綵  E3 5C   &#x7DB5;<br />
臀  E4 5C   &#x81C0;<br />
藹  E5 5C   &#x85F9;<br />
觸  E6 5C   &#x89F8;<br />
軆  E7 5C   &#x8EC6;<br />
鐔  E8 5C   &#x9414;<br />
饅  E9 5C   &#x9945;<br />
鷭  EA 5C   &#x9DED;<br />
偆  ED 5C   &#x5046;<br />
砡  EE 5C   &#x7821;<br />
続きを読む

Perl で、メールサーバに送られてきたメールを処理する

Procmail を利用すればサーバに送られてきたメールが標準入力から Perl スクリプトに渡すことができる。ここでは Procmail の詳細は割愛するが、以下は標準入力に入っているメールを処理する関数。

使用するクラス


use MIME::Parser;
use MIME::Base64;
use Mail::Address;
use MIME::Parser; use MIME::Base64; use Mail::Address; use Unicode::Japanese; my ($mail) = &parse; print "From: " . $mail->{sender} . "\n"; print "To: " . $mail->{recipient} . "\n"; print "Subject: " . $mail->{subject} . "\n"; sub parse {
my ($mail);

# Parser Setting
my $parser = new MIME::Parser;
$parser->output_to_core(1);    # Keeps body analyzed internally
$parser->tmp_recycling (1);    # Recycles the temporary stuff

my $entity = $parser->parse(\*STDIN) or die "Parse Error in __FILE__\n";

# Headers
$mail->{sender   } =  $entity->head->get('from'   );
$mail->{subject  } =  $entity->head->get('subject');
$mail->{recipient} =  $entity->head->get('to'     );
$mail->{recipient} =~ s/\n//g;

# From
@addrs = Mail::Address->parse($mail->{sender});
foreach $addr (@addrs) {
    $mail->{sender} = $addr->address if $addr->address; # USE CAUTION, for the multiple address
}

# Subject
my $subject = $mail->{subject};
$lws      = '(?:(?:\x0D\x0A|\x0D|\x0A)?[ \t])+';
$ew_regex = '=\?ISO-2022-JP\?B\?([A-Za-z0-9+/]+=*)\?=';
$subject  =~ s/\n//g;
$subject  =~ s/($ew_regex)$lws(?=$ew_regex)/$1/gio;
$subject  =~ s/$lws/ /go;
$subject  =~ s/$ew_regex/decode_base64($1)/egio;
$subject  = Unicode::Japanese->new($subject, 'auto')->sjis_imode;
$mail->{subject} = $subject;

# Get MIME
($mail->{body}, $mail->{filename}, $mail->{mimetype}, $mail->{object}) = &getEntities($entity);
$mail->{body   } =  Unicode::Japanese->new($mail->{body}, 'auto')->sjis_imode;
$mail->{body   } =~ s/[  \n\r]+$//g;    # Delete the white spaces in the end of the line
$mail->{subject} =~ s/[  \n\r]+$//g;    # Delete the white spaces in the end of the line

return $mail;
} sub getEntities {
my $entity = shift;
my $body, $filename, $mimetype, $object;

# BODY
my @parts = $entity->parts;
if (@parts) {                   # multipart...

    my $i;
    foreach $i (0 .. $#parts) { # dump each part...

        &getEntities($parts[$i]);
    }
} else {                        # single part...

    # Get MIME type, and display accordingly...
    my ($type, $subtype) = split('/', $entity->head->mime_type);
    my $bodyhandle = $entity->bodyhandle;

    if ($type =~ /^(text|message)$/) {      # text

        $body .= $bodyhandle->as_string;

    } else {                                # binary

        $filename= $entity->head->recommended_filename;
        $mimetype = $entity->head->mime_type;
        $object = $bodyhandle->as_string;
        binmode($object);
        # if we want to store it as BASE64, you can continue the processs here
    }
}

return ($body, $filename, $mimetype, $object);
} 1;
続きを読む

Perl で、アップロードしたファイルを処理する

Perl の場合は、CGI として動作させる場合は、必ず


print qq|Content-type: text/html\n\n|;

という行が必要な以外は、PHP とほとんど同じである。 アップロードには、いつも使っているおなじみの CGI モジュールを使う。


use CGI;

そうすると、以下のようにアクセスできる。


our $CGI = new CGI;
...
my $xml = $CGI->param('file');

$xml はファイルポインタが入っている。通常は open(IN, "..."); などするときの IN にあたるものである。以下は、コード。アップロードする側の HTML ファイルは PHP でアップロードファイルを処理するで紹介したのと同じ。

#!/usr/bin/perl use strict; use CGI; our $CGI = new CGI; print qq|Content-type: text/html\n\n|; init(); sub init() {
# XMLパーサー作成 ('UTF-8')
my $xml_parser = new XML::Parser(    ProtocolEncoding => 'UTF-8',
                                    Handlers=>{    Start=>\&startElement,
                                                End  =>\&endElement,
                                                Char =>\&dataHandler    });
# ファイルのオープンはいらない。input タグで指定した name 属性がそのままファイルハンドルになる
my $xml = $CGI->param('file');

# XMLパース処理
$xml_parser->parse($xml) or    die "XML error: $xml_parser";

# ファイルクローズ
close($xml);
} 1;
続きを読む

Perl で、HTML 文書中にあるリンクを置換する

以下は、HTML 文書中にあるリンクを任意の文字列に置換するスクリプトであるが、他のタグでもいろいろと応用が利くと思われる。

#!/usr/bin/perl use LWP::UserAgent; use HTTP::Status; use HTTP::Response; our $URL = 'https://perltips.twinkle.cc/'; # アクセスする URL my $proxy = new LWP::UserAgent; $proxy->agent('your own created browser name here'); $proxy->timeout(60); my $response = $proxy->request(HTTP::Request->new('GET' => $URL)); my $content = $response->content; my %tags = (
'img'  => 'src',
'a'    => 'href',
'link' => 'href',
'td'   => 'background',
'form' => 'action'
); my $data = $content; skip_others: while($data =~ s/<([^>]*)>// && $i++ < 10000) { # 無限ループに陥るのを防ぐ <pre>my $in_brackets = $1; foreach $key (keys %tags) { if($in_brackets =~ /^\s*$key\s+/i) { if($in_brackets =~ /\s+$tags{$key}\s*=\s*["']([^"']*)["']/i || $in_brackets =~ /\s+$tags{$key}\s*=\s*([^\s]+)/i ) { my $link = $1; $link =~ s/[\n\r]//g; $link =~ s/\./\\./g; $link =~ s/\?/\\?/g; $content =~ s/\s*=\s*["']*$link["']*/=<<<ここに置換文字列を書く>>>/ if $link !~ m/mailto:/i && $link !~ m/javascript/i; next skip_others; } } } } 1;
続きを読む
CGI を購読