中年プログラマーの息抜き

ブログをはじめました。気の向くままにプログラム関連ネタをメモしていきます。

メール受信からプログラムを実行し添付ファイル保存する

はじめに

レンタルサーバー(XSERVER)、メール受信をトリガーとし、プログラムを起動します。※Lambda Function的な...。
今回は、特定のアドレスがメール受信した時に、Perl5 のプログラム import_attachment.pl を実行する例を紹介します。

手順

トリガー設定(例)は、
 ⇒ Xserverレンタルサーバー|サーバーパネルから「メールの振り分け」をクリック
 ⇒ ドメイン選択画面から「設定対象のドメイン:選択する」をクリック
 ⇒ メール振り分け設定画面から「メール振り分け設定追加」をクリック

f:id:tm-b:20210906175202j:plain

・条件1(キーワード):特定のアドレス
・条件1(場所):あて先
・条件1(一致):内容を含む
・条件1(宛先):| /usr/bin/perl /home/(サーバID)/bin/import_attachment.pl
・処理方法(配送方法):転送

プログラム(import_attachment.pl)

/home/(サーバID)/bin/import_attachment.plを作りパーミッション「700」に設定すれば動くはず。

#!/usr/bin/perl

use strict;
use warnings;
use utf8;
use Time::HiRes "gettimeofday";
use MIME::Parser;

my @accepts = ('受け付けるメール送信者:メールアドレス');
my $output = '添付ファイル保存先のフルパス';
my $log = '実行ログファイルのフルパス';
my ($es, $ms) = gettimeofday();
my ($S, $M, $H, $d, $m, $y) = localtime;
($y, $m, $d, $H, $M, $S, $ms) = ($y + 1900, sprintf("%02d", ++$m), sprintf("%02d", $d), sprintf("%02d", $H), sprintf("%02d", $M), sprintf("%02d", $S), sprintf("%06d", $ms));

open(LOG, ">>$log") || die;
flock(LOG, 2) || die;
print LOG "\n[$y/$m/$d $H:$M:$S.$ms] START, ";

my $mp= new MIME::Parser;
$mp->output_to_core(1);
$mp->tmp_to_core(1);
$mp->tmp_recycling(1);
$mp->use_inner_files(1);

my $mail= $mp->parse(\*STDIN);
my @from = $mail->head->get('From') =~ /<(.+)>/;
print LOG " from=$from[0], ";

@from = grep { $_ eq $from[0] } @accepts;
if (scalar(@from) > 0 && $mail->is_multipart) {
    my $count = $mail->parts;
    for (my $i = 0; $i < $count; $i++) {
        my $entity = $mail->parts($i);

        my $mime = $entity->head->mime_type;
        print LOG " mime=$mime, ";

        my $name = "$output/$y$m$d$H$M$S$ms".sprintf("%04d", rand(1000));
        if ($mime eq "image/jpeg") {
            $name .= ".jpg"
        } elsif ($mime eq "application/pdf") {
            $name .= ".pdf"
        } else {
            next;
        }
        print LOG " name=$name ";

        my $body = $entity->bodyhandle;
        my $IN = $body->open("r") || next;
        my $data = $IN->getline();
        if (defined($data)) {
            print LOG " (save)";
            open(OUT, ">$name") || next;
            eval {
                do {
                    print OUT $data;
                } while (defined($data = $IN->getline()));
                close(OUT);
                chmod 0600, $name;
            };
            if (my $e = $@) {
                print LOG "$e, ";
            } else {
                print LOG ", ";
            }
        } else {
            print LOG " (skip), ";
        }
        $IN->close;
    }
    print LOG " END ";
} else {
    print LOG " IGNORE ";
}

flock(LOG, 8);
close(LOG);

1;

まとめ

プログラムが異常終了したときは、メール送信者にその内容が自動送信されるようでした。。自前でログを書きだしていくのは大変ですね。しみじみ
「perl5」、どうかなって思いつつ、楽なので書き進めましたが、流行的にも気が向いたら同じ処理を「python3」で、書き直したいなとさ。

PHP 自動定数(__FILE__、__DIR__)からシンボリックリンクのパスをそのまま利用

はじめに

5年くらい前、サーバ内にいくつかのWordpressをデプロイするときに、設定ファイルとデータベース以外はシンボリックリンクで扱えるように(毎回Wordpress一式をコピーしなくても量産できる)方法を調べたことがあったなと思い出したので、今更ながら思い出せる範囲でメモってみる。

tm-b.hatenablog.com

PHP言語の標準動作はこうですね。

シンボリックリンクを解決した後の・・・

www.php.net

ということでPHPソースをいじる

シンボリックリンクを解決する前の・・・

どこを触ったかなって思い出してますが、1つはここ・・だった気がします。(前回の記事でメモっとおけばよかった。。時間とともに忘れる)

php-7.1.0/Zend/zend_language_scanner.c

556行目付近

 

修正前

if (file_handle->opened_path) {
    compiled_filename = zend_string_copy(file_handle->opened_path);
} else {
    compiled_filename = zend_string_init(file_handle->filename, strlen(file_handle->filename), 0);
}

 

修正後

if (file_handle->opened_path) {
    file_handle->opened_path = zend_string_init(file_handle->filename, strlen(file_handle->filename), 0);
    compiled_filename = zend_string_copy(file_handle->opened_path);
} else {
    compiled_filename = zend_string_init(file_handle->filename, strlen(file_handle->filename), 0);
}

この流れでインストール

ソースを触るので、yumなどのパッケージマネージャ経由ではインストールできませんよね。当たり前ですが・・ PHPソースから「 configure make make install 」

まとめ

これ以外にも何か触った気がしますが、、また思い出したら追記かな

セキュリティ対策などPHP4位からシンボリックリンクのパスのそのままが取得できなくなったと記憶してますが、自分で使うクローズされたプログラムであれば、まあ言語仕様を拡張(変更)する的なことも面白かったですよ。良い思い出ですね。 気が向いたら当時のVMを起動してコンパイルしたときのソースでも確認してみようかと思ったのでした。(rootのパスワードなんだっけってなりそう(汗))

・・ 5年たった今では、ソースを追えないかもしれない(泣)・・

 

PHP Imagick でPDFをPNGに変換

はじめに

今回、DBに格納したPDFバイナリ(BLOB)をAPI側でPNGに変換してレスポンスする処理をメモっときます。

 

いきなりですがPHPコード

    $im = new imagick();
    $im->setResolution(96, 96);
    $im->readimageblob(/**    SELECTしたBLOB    **/);
    $im->setImageIndex(0);
    $im->setImageBackgroundColor('#ffffff');
    $im->setImageAlphaChannel(Imagick::ALPHACHANNEL_REMOVE);
    $im->mergeImageLayers(Imagick::LAYERMETHOD_FLATTEN);
    $im->setImageFormat("png");
    $im->setOption('png:compression-level', 5);
    $im->stripImage();
    $im->thumbnailImage(/**  表示幅  **/, 0);

$image = $im->getImageBlob();
header('Content-Description: Image File'); header("Content-Type: image/png;"); header('Expires: 0'); header('Cache-Control: must-revalidate'); header('Pragma: public'); header('Content-Disposition: inline'); header('Content-Length: ' . strlen($image)); echo $image; exit;

ざっくり補足

setImageIndex(0) 、 今回のPDFは1ページだけなので、ループなし。

ヘッダ「Content-Type」 、 HttpClientでは取得できなかった。

出力で四隅に余白ができるので、もう少し処理を足せばぴったり作れる・・が、

(今回は余白できても良かったので気にしない感じ)

setResolution(96, 96)、 Windowsを意識した。

thumbnailImage(/** 表示幅 **/, 0) 、 今回のPDFはA4限定だから、高さはゼロで良い。

setOption('png:compression-level', 5)、 公式ドキュメント参照

www.php.net