Tamashiro.org

ホーム·登録·クイック登録·クイック変更·一括変更
28 件中 1 - 10 件目
表示件数:102050
1·2·3·次の10件 >>
登録日時
更新日時
内容
2011/12/30 17:10

EncodeMIMEHeader - 全ての文字を1行76バイト以内にMIMEヘッダエンコードする

.mailfilterに続きNTTドコモの「メール使いホーダイ」ネタ第3弾。

ケータイからFacebookに投稿したいけど、Facebookの投稿用メールアドレス宛に送信しても反映されるのはメールの件名に書いた分だけで、メール本文は無効なのです。ぼくのケータイは件名が30バイトまでしか対応していない古いタイプなので、これでは短い文章しか投稿できない!

なので本文を件名に埋めて投稿用メールアドレス宛に再送信するプログラムを作ったのですが、Encode::MIME::Headerでエンコードした文字列がFacebook側でうまくデコードされなかったのです。どうやら$especialsで定義されている文字が含まれているとうまくいかない。

いろいろ考えた結果、RFCを満たさんけどもう全部エンコードしてしまおうと、でも守れるところは守ろうと1行は76バイト以内に収めるエンコードモジュールを作りました。
フィールド名(Subject: とか)を含めた1行のUTF8フラグ付き文字列を引数に指定すると、戻り値にMIMEヘッダエンコードした文字列が得られます。

概要

use EncodeMIMEHeader qw( encode_MIME_header );

use utf8;
my $header = 'Subject: 食卓の電灯、電球が切れたので LED に変えました!Amazon で 1,745 円 x 3 灯、熱くなくて明るさも十分。良いです!';

print encode_MIME_header($header);
Subject: =?UTF-8?B?6aOf5Y2T44Gu6Zu754Gv44CB6Zu755CD44GM5YiH44KM44Gf44Gu?=
 =?UTF-8?B?44GnIExFRCDjgavlpInjgYjjgb7jgZfjgZ/vvIFBbWF6b24g44GnIDEsNzQ1?=
 =?UTF-8?B?IOWGhiB4IDMg54Gv44CB54ax44GP44Gq44GP44Gm5piO44KL44GV44KC5Y2B?=
 =?UTF-8?B?5YiG44CC6Imv44GE44Gn44GZ77yB?=

ソース

2011/7/9 11:20

Waftで304 Not Modifiedを出力するコード

最近いろいろなプログラムで使い回している、Waftで304 Not Modifiedを出力するためのコード。本体に組み込もうかなぁ。

sub Waft::not_modified_since {
    my ($self, $last_modified_time) = @_;

    return if not $last_modified_time;
    return if not eval { require HTTP::Date };

    my $modified_time = HTTP::Date::str2time($ENV{HTTP_IF_MODIFIED_SINCE});

    if ( $modified_time and $modified_time == $last_modified_time ) {
        $self->add_response_header('Status: 304 Not Modified');
        $self->output;

        return 1;
    }

    my $last_modified_string = HTTP::Date::time2str($last_modified_time);
    $self->add_response_header("Last-Modified: $last_modified_string");

    return;
}

使い方は以下のとおり。directメソッド等の中で呼び出して、戻り値が真なら処理を終了させます。引数にコンテンツの更新日時をUNIX time(time関数で得られる形式)で指定してください。

sub __default__direct {
    my ($self) = @_;

    my $last_modified_time = ~; # データベース等から更新日時を取ってくる

    return if $self->not_modified_since($last_modified_time);

    return 'TEMPLATE';
}
2011/7/3 0:18

.mailfilterでケータイへの転送メールを一部制限(さくらのレンタルサーバを使用)

NTTドコモの「メール使いホーダイ」を契約したので、メールだけはパケット代を気にせずケータイで送受信できるようになりました。PCで使っているメールもケータイに転送設定しておくと便利♪ではありますが、メールアドレスをWebにさらしているために迷惑メールがハンパない訳で、これが全部ケータイに転送されるととっても困ります。
そこでちょっと工夫が必要になりました。

メールサーバ内でSpamAssassinが動いてくれているので、これを使ってケータイへの転送を振り分けてみます。SpamAssassinが迷惑メールの程度をX-Spam-Levelで示してくれる(「*」が多いほど迷惑メールである可能性が高い)ので、まずその程度を決めてみました。
PCのThunderbirdで受け取っているこれまでのメールを「メッセージの検索」で検索してみます。カスタムヘッダで「X-Spam-Level」を追加して「**」で検索すると、迷惑メールではないメールがちらほらとヒット。「***」で検索してみると迷惑メールのみになったので、X-Spam-Levelが3未満のメールをケータイに転送する事にしました。

.mailfilterに以下を設定。

if ( ! ( /^X-Spam-Level: \*{3,}/ ) )
{
        cc "!ケータイのメールアドレス"
}

これでめでたく迷惑メールでないメールだけがケータイに転送されるようになりました。

の、はずだったのですが、それでも次々に転送されてくる迷惑メール。どのメールも、件名に「=?koi8-r?B?~」とケータイでデコードされないMIME。koi8-rってどこ?(ロシア、でした。)このメールだけはX-Spam-Levelが3以上にならないので、仕方なく個別に転送を拒否します。

if ( ! ( /^X-Spam-Level: \*{3,}/ || /^Subject: =\?koi8-r\?B\?/ ) )
{
        cc "!ケータイのメールアドレス"
}

ひとまずこれに落ち着きました。

しばらく経って、少し変更。
ケータイ側のメールサーバの仕様をいまいち把握していなくて、もし何らかの理由でケータイ側のメールサーバに転送メールの受け取りを拒否されると、エラーメールが送信元に行ってしまうなぁ、と。
メールの転送ではよく聞く事例ですが、このエラーメールには転送先のメールアドレスとそれに配送できなかった理由が記載されるので、つまりメールの送信者に転送先の(ケータイの)メールアドレスを知られてしまいます。それが迷惑メール送信者だったらもうサイテーです。

なので、メールを転送する時にenvelope sender、いわゆる送信元(Envelope-FromやSenderとも)を変更する事にしました。

if ( ! ( /^X-Spam-Level: \*{3,}/ || /^Subject: =\?koi8-r\?B\?/ ) )
{
        cc "| /usr/sbin/sendmail -i -f 送信元メールアドレス ケータイのメールアドレス"
}

直接ケータイのメールアドレスを指定して転送させるのではなく、メールをsendmailに渡してケータイに送信させます。その際にsendmailの-fオプションで送信元のメールアドレス、つまりエラーメールの送信先とするメールアドレスを指定します。
メールの本文に先頭が.(ドット)の行があってもそのまま転送できるよう-iも指定しています。間違っても-tは付けないように!

これで、配送エラーが発生した場合のエラーメールは、元の送信者に送信される事なく、指定した送信元メールアドレス宛に届くようになりました。(指定するケータイのメールアドレスを存在しないメールアドレスに変えて試してみると確認できます。)メールのループが発生しないよう、送信元メールアドレスには転送設定をしない別のメールアドレスを指定しましょう。

2011/6/10 10:18

メールをトリガーに任意のPerlスクリプトを実行する(さくらのレンタルサーバを使用)

ケータイのパケット定額サービスの料金は数千円とまだまだ高く契約を躊躇してしまいます。でも外出中にネットにつなげられるとやっぱり便利ですよね。
そんな中、NTTドコモの「メール使いホーダイ」を発見。メールだけならお安いのね。例えば件名「web」本文にWebアドレスを入力してメール送信したらページ内容がメールで送られてくる、とか、件名「google」本文に検索したい文字列なら検索結果、とか、「whois」とか、「nslookup」とか、何とかかんとか、いろいろできそうやん!と、セコい考えを思い立ってしまったわけです。

ひとまず、メールを受信してPerlスクリプトを実行するところまでをざーっと作ってみました。

1. スクリプトファイルをサーバに置く

末尾のスクリプトをサーバに置いてください。これ以降、以下の条件で記載するので適宜読みかえてくださいね。

アカウント名                : ACCOUNT
スクリプトファイルを置く場所: /home/ACCOUNT/www/bin/
スクリプトのファイル名      : run.pl
メールアドレス              : run

安全のため、http経由では実行できないよう、スクリプトファイルを置く場所には以下のよう設定しましょう。.htaccessでアクセス制限しておくとより安心です。

% chmod 700 /home/ACCOUNT/www/bin

run.plには実行フラグを付与します。

% chmod +x /home/ACCOUNT/www/bin/run.pl

2. run()の中に任意の処理を書く

run.pl先頭部のrunサブルーチンの中に、実行したい処理を書いてください。第1引数にメールの件名、第2引数にメールの本文が渡されます。
メールの文字コードに依らず、いずれもUTF-8に変換され、UTF8フラグも付きます。

3. メールアドレスを作成する

サーバコントロールパネルでrunというメールアドレスを作成してください。

4. .mailfilterを作成する

作成したメールアドレスに.mailfilterを設定します。

% vi /home/ACCOUNT/MailBox/run/.mailfilter

以下の内容を記述してください。

if ( (1) )
{
        to "| /home/ACCOUNT/www/bin/run.pl"
}

if文で囲んだのは、サーバコントロールパネルからメールの設定変更をしても内容が上書きされないようにするためです。それをしないのであれば、toの行だけでも良いと思います。

あと、ファイルモードを600にしないといけません。

% chmod 600 /home/ACCOUNT/MailBox/run/.mailfilter

※この.mailfilterの設定が重要なのですが、公式な機能では無いと思いますので、行う場合は自己責任の下でお願いします。設定方法は「さくらのレンタルサーバ非公式FAQ」を参考にしました。感謝です!

メール < Wiki | さくらのレンタルサーバ非公式FAQ
http://faq.sakuratan.com/wiki/wiki.cgi?%a5%e1%a1%bc%a5%eb#i37

※でも、カスタマーセンターから.mailfiterに関する回答の引用があったり、あと実際に公式のFAQにも.mailfilterの記載があったり、と実は公式な機能なのかもしれません。

5. 完了!

これで、run宛にメールを送信したらrun.plが実行されます。

スクリプト

#!/usr/bin/perl

use strict;
use warnings;

sub run {
    my ($subject, $body) = @_;

    # ここに任意の処理を書いてください。メールを受信する度に実行されます

    # $subject と $body はいずれも UTF-8 で、UTF8フラグも付いています

}

#
# メールを解析して Subject と body を取り出し、run() に渡す
#

# 標準エラー出力は /dev/null 行き
BEGIN { open STDERR, '>', '/dev/null' }

# カレントディレクトリを run.pl と同じディレクトリに移動する
use FindBin qw( $Bin );
BEGIN { chdir $Bin }

require Encode;
require MIME::Parser;

my $parser = MIME::Parser->new;

# 用途上、メールのサイズはそんなに大きくならないので、オンメモリで処理する
$parser->output_to_core(1);

my $entity = $parser->parse(\*STDIN);

#
# まず Subject
#

# Subject を取り出して、UTF-8 にする
my $encoded_subject = $entity->head->get('Subject');
my $subject = eval { Encode::decode('MIME-Header', $encoded_subject) };

if ( $@ ) {
    # MIME-Header の decode に失敗したら、仕方ないのでそのまま
    $subject = $encoded_subject;
}

# 余分な空白文字が含まれる事があるので除去
$subject =~ s/\A \s+ | \s+ \z//gxms;

#
# 次に body
#

# multipart の場合は最初のパートのみを対象にする
my $first_part = $entity->is_multipart ? $entity->parts(0) : $entity;

# body を取り出して、UTF-8 にする
my $charset = $first_part->head->mime_attr('Content-Type.charset');
my $encoded_body = $first_part->bodyhandle->as_string;
my $body = eval { Encode::decode($charset, $encoded_body) };

if ( $@ ) {
    # Subject と同様、失敗したらそのまま
    $body = $encoded_body;
}

#
# 処理を実行
#

run($subject, $body);

exit;
2011/6/9 5:35

正規表現の先読みと後読みを使いこなす

Perlにはルックアラウンドアサーション(lookaround assertion)という4つの正規表現拡張があります。肯定の先読み・否定の先読み・肯定の後読み・否定の後読みの4つです。

(?=...) : 肯定の先読み
(?!...) : 否定の先読み
(?<=...): 肯定の後読み
(?<!...): 否定の後読み

これらの使い方を紹介します。

例えば、HTMLのタグとタグの間の空白文字をs///演算子で全て取り除きます。
ルックアラウンドを使わない場合は、

$html =~ s/>\s+</></g;                    # ルックアラウンドを使わない

と書けます。少し読みにくくなるので、Perlベストプラクティスで書きます。

$html =~ s{ > \s+ < }{><}gxms;            # ルックアラウンドを使わない。Perlベストプラクティスの書き方

となります。

ですが、置き換える必要のない山カッコまで置き換えるのはスマートではないですよね?
そこでルックアラウンドを使います。

直前に左山カッコが現れる位置を肯定の後読みでマッチさせて、直後に右山カッコが現れる位置を肯定の先読みでマッチさせる事で、山カッコを置き換えず、空白文字だけを置き換える(ここでは取り除く)対象にできます。

$html =~ s{ (?<= > ) \s+ (?= < ) }{}gxms; # ルックアラウンドを使う

ただ、これではファイル先頭もしくは末尾に空白文字がある場合にそれを取り除く事ができません。
それも取り除くには次のように、先頭および末尾にもマッチする以下のパターンでも良いですが、

$html =~ s{ (?: \A | (?<= > ) ) \s+ (?: (?= < ) | \z ) }{}gxms;

と長くなってしまいます。

否定の後読みおよび否定の先読みと文字クラスの否定を使うと、以下のようにもっとシンプルに表現できます。

$html =~ s{ (?<! [^>] ) \s+ (?! [^<] ) }{}gxms;

これは、

(?<![^>]): 直前に右山カッコ以外の文字は現れない
(?![^<]) : 直後に左山カッコ以外の文字は現れない

という表現になり、つまり、

(?<![^>]): 直前に右山カッコが現れる、もしくは文字が無い
(?![^<]) : 直後に左山カッコが現れる、もしくは文字が無い

という意味になるためです。おもしろいですね!

2010/9/29 11:02

Synergyが動作しない場合はVisual C++のランタイムをインストールする

素のWindows XP SP3にSynergyをインストールしても動作せず。Visual C++のランタイムをインストールしたら動作するようになりました。

ダウンロードの詳細 : Visual C++ 2008 SP1 再頒布可能パッケージ (x86)
http://www.microsoft.com/downloads/details.aspx?familyid=A5C84275-3B97-4AB7-A40D-3802B2AF5FC2&displaylang=ja
2010/8/29 7:03
Windows 7 でユーザーのプロファイルを初期化する

1. 再起動
2. 対象ユーザー以外のユーザーでログイン
3. C:\Users\<対象ユーザー> を削除
4. HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\<対象ユーザーの SID> を削除
2010/6/6 14:30

Tokyo Cabinet + Starmanで住所検索

高速なKVSであるTokyo Cabinetと高速なWebサーバStarmanを試してみたいなぁ、と思い、Ajaxを使った簡単な住所検索アプリを作ってみました。

※wataさんが、Tokyo Cabinetの後継のKyoto Cabinetを紹介されています。

Kyoto Cabinet + Starmanで住所検索 ...を試してみた - ワタブログ
http://wata-jp.ldblog.jp/archives/1652876.html

1. PlackとStarmanをインストール

CPANでPlackとStarmanをインストールします。

% sudo cpan Plack
% sudo cpan Starman

2. Tokyo CabinetとPerl用APIをインストール

ソースパッケージとPerl用APIをダウンロードします。

データベースマネージャ Tokyo Cabinet
http://1978th.net/tokyocabinet/
tokyocabinet-1.4.45.tar.gz
perlpkg/tokyocabinet-perl-1.33.tar.gz

まずはTokyo Cabinetのインストール。私の環境(Ubuntu 9.10)ではbzlib.hが不足してたので、先にlibbz2-devをインストールしておきました。

% tar xzvf tokyocabinet-1.4.45.tar.gz
% cd tokyocabinet-1.4.45
% ./configure --prefix=/usr/local/tokyocabinet
% make
% sudo make install

次にPerl用APIをインストールします。

% tar xzvf tokyocabinet-perl-1.33.tar.gz
% cd tokyocabinet-perl-1.33
% PATH=$PATH:/usr/local/tokyocabinet/bin perl Makefile.PL
% make
% make test
% sudo make install

3. KEN_ALL.tchを作成

住所検索の情報となる郵便番号データを日本郵便のサイトからダウンロードします。“読み仮名データの促音・拗音を小書きで表記しないもの”の“全国一括”のken_all.lzhです。

郵便番号データダウンロード - 日本郵便
http://www.post.japanpost.jp/zipcode/download.html

これを展開したKEN_ALL.CSVから、以下のスクリプトを使ってTokyo Cabinetのハッシュデーベースファイルを作ります。今回作る住所検索アプリでは郵便番号を1桁入力した時点から候補を表示するようにしたいので、1桁~7桁のパターン毎に10個までの候補を記録しておきます。テーブルデータベースを使うともっとスマートに構成できそう?
また、Perl用APIはOOで扱う事ができますが、今回は手軽にtieを使いました。

#!/usr/bin/perl

use 5.008001;
use utf8;

use strict;
use warnings;

open my $ken_all_csv, '<:encoding(shiftjis)', 'KEN_ALL.CSV'
    or die 'failed to open KEN_ALL.csv';

my %ken_all_tmp;

{
    local $/ = "\x0D\x0A";

    while ( my $record = <$ken_all_csv> ) {
        my ($zip_code, @address) = ( split /,/, $record )[2, 6, 7, 8];
       
        for my $value ($zip_code, @address) {
            $value =~ s/\A "(.*)" \z/$1/xms;
        }

        if ( $address[2] eq '以下に掲載がない場合'
             or $address[2] =~ / の次に番地がくる場合 \z/xms
        ) {
            $address[2] = q{};
        }
        else {
            $address[2] =~ s/ 一円 \z//xms;
        }

        my $address = join q{}, @address;

        DIGIT:
        for my $digit ( 1 .. 7 ) {
            my $zip_code = substr $zip_code, 0, $digit;

            next DIGIT if @{ $ken_all_tmp{$zip_code} ||= [] } >= 10;

            push @{ $ken_all_tmp{$zip_code} }, $address;
        }
    }
}

use TokyoCabinet;

use constant OWRITER => TokyoCabinet::HDB::OWRITER;
use constant OCREAT  => TokyoCabinet::HDB::OCREAT;

tie(my %ken_all, 'TokyoCabinet::HDB', 'KEN_ALL.tch', OWRITER | OCREAT)
    or die 'tie error';

while ( my ($zip_code, $addresses) = each %ken_all_tmp ) {
    $ken_all{$zip_code} = join q{<br />}, @$addresses;
}

4. PSGIを作成

住所検索アプリとなるプログラムをPSGI仕様で作成します。こちらもtieを使いました。
Ajaxリクエストのトリガーとする郵便番号の入力捕捉は、keydownイベントをそのまま使い、キーコードも使わずsetTimeoutでタイミングをずらして値をそのまま取得しています。


use strict;
use warnings;

use TokyoCabinet;

use constant OREADER => TokyoCabinet::HDB::OREADER;

my $html = do { local $/; <DATA> };

my $app = sub {
    my ($env) = @_;

    return [ 200, [ 'Content-Type' => 'text/html' ], [ $html ] ]
        if $env->{REQUEST_URI} !~ m{\A /search\?zip_code=(.*) }xms;

    my $zip_code = $1;

    tie(my %ken_all, 'TokyoCabinet::HDB', 'KEN_ALL.tch', OREADER)
        or die 'tie error';

    my $address = $ken_all{$zip_code} || q{};

    return [ 200, [ 'Content-Type' => 'text/html' ], [ $address ] ];
};

__DATA__
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja" lang="ja">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>ken_all</title>
<script type="text/javascript" src="http://www.google.com/jsapi"></script>
<script type="text/javascript"><!--
  google.load('jquery', '1.4'); // -->
</script>
<script type="text/javascript"><!--
  jQuery( function ($) {
      $('input#zip_code').keydown( function () {
          var $this = $(this);

          setTimeout( function () {
              $.get( '/search', { zip_code: $this.val() }, function (data) {
                  $('div#address').html(data);
              } );
          }, 0 );
      } );

      $('input#zip_code').focus();
  } ); // -->
</script>
</head>
<body>
<form action="/search" method="get">
  <div><input id="zip_code" type="text" /></div>
  <div id="address"></div>
</form>
</body>
</html>

5. PSGIを起動してブラウザでアクセス

作成したPSGIをStarmanで起動します。デフォルトのポート5000でWebサーバとして動作します。これで準備完了です。

% starman ken_all.psgi

ブラウザからWebサーバにアクセスします。ローカルホストであればURLはhttp://localhost:5000/です。テキスト入力欄に数値を入力すると、その下に瞬時に住所が表示されます。速い!

2010/5/8 13:24

“「rel="canonical"」で自分自身のURLを示すのは良い手法”

「rel="canonical"」で自分自身のURLを示すのは良い手法? 悪い手法? | Web担当者Forum
http://web-tan.forum.impressrd.jp/e/2010/04/19/7785
そうだよ!正規バージョンのページのrel="canonical"タグで、正規バージョンページ自身を参照させて構わないんだ。君の意図はちゃんと分かるさ。
2010/5/5 20:21

LFshell - PerlとJavaScriptで動くシェルライクなインターフェース

LFshellはPerlとJavaScriptで動くシェルライクなインターフェースです。

サマリー

管理者

玉城 勇二 気ままにでもなるべく正確に、人に迷惑をかけないよう心がけて書いています。ご意見・ご感想は管理者のページからメッセージ送信してください。お待ちしています。

1·2·3·次の10件 >>
Perl, Waft, jQuery
LFdata 0.0018