Laravel メールアドレスを画像化してスパムを防止する方法

さてさて、このブログを始めるずっと前からですが、私は他にも「ぷれりり・プレスリリース」という個人情報なしでプレスリリースが公開できる無料サイトを運営しています。

元々いろいろとウェブサービスを作ってきた(結果閉鎖もしてきた ^^;)こともあって、世の中のサイト・オープンを少しでも多く知ってもらえる機会があればいいと思い、ほぼボランティアのような形で運営しています。

そして、今回の話題はこのプレスリリース・サイトでも使っているテクニックで、「メールアドレスを画像化してスパムから守る」というものです。

プレスリリースはその性質上、よくメールアドレスや電話番号などを記載しているのですが、メールアドレスをそのままインターネット上に載せてしまうと機械的にメールアドレスを収集していろいろな広告を送りつける、いわゆる「スパムメール」の標的になってしまうことも有名な話です。

そこで、ひとつの対策法として、メールアドレスをテキストではなく画像化してしまうというテクニックがあります。こうしておくと(もちろんAIやOCRを使われると完全とは言えませんが)メールアドレスの収集は防止することができるわけですね。

ということで、今回はLaravelを使ってメールアドレスを画像化する方法をご紹介します。
最後にはソースコード一式がダウンロードできますので、ぜひサイト開発に役立ててくださいね。

※ 開発環境はLaravel 5.7です。

パッケージをインストールする

今回は画像を扱うことになりますので、完全網羅!Intervention Image(PHP)で画像を編集する全実例でも紹介したパッケージIntervention Imageをインストールしておいてください。インストールする方法もこの記事の中に書いてあります。

テスト文字

テストで使う会社情報は次のものとします。

Example株式会社
test@example.com

※ ちなみに、example.comは今回のようにサンプルを表示するためのドメインなので、リンクを貼ろうが、メール送信しようが誰にも迷惑をかけることがありません。なのでLaravelのconfig/mail.php内のメールアドレス(初期値)もhello@example.comとなっています。

今回の開発ではこの「test@example.com」の部分を画像化していきます。

ルーティング、コントローラーを作る

まずは「Example株式会社」の会社情報が通常通り表示されるページをつくります。

ルーティング

ルーティングは以下の2つです。
1つ目は会社情報が表示されるページで、2つ目はメールアドレス画像のページです。

Route::get('test/email_image', 'TestController@email_image');
Route::get('image/email/{code}', 'ImageController@email');

ビューをつくる

では、次にビューです。
resources/views/email_image.blade.phpというファイルを作成して、その中にコンテンツを追加します。

<html>
<body>
    <div>
        {!! $email_text !!}
    </div>
</body>
</html>

つまり、$email_textにメールアドレスを含んだテキストが入ってきます。

コントローラーをつくる

必要になるコントローラーはTestControllerImageControllerの2つです。
以下のコマンドで作成してください。

php artisan make:controller TestController
php artisan make:controller ImageController

そして、TestControllerを開いてemail_image()を追加します。

public function email_image() {

    $email_text = 'Example株式会社<br>test@example.com';
    return view('email_image')
        ->with('email_text', $email_text);

}

このメソッドの中で、ビューを設定し、その中で会社情報を変数として使えるようにしています。

そして、実際にページにアクセスするとこうなります。

※ もちろん、まだ全てテキストです。

メールアドレスを画像化する

では、この「test@example.com」の部分だけを画像に入れ替えます。

メールアドレスを正規表現で<img>タグと入れ替える

メールアドレスの部分だけをテキスト内から抜き出すには通常preg_replace()を使いますが、今回は抜き出したメールアドレスの加工も必要なので、さらに複雑な処理ができるpreg_replace_callback()を使います。

$email_text = 'Example株式会社<br>test@example.com';

$pattern = '/(?:[a-z0-9!#$%&\'*+\/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&\'*+\/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])/';
$email_image_text = preg_replace_callback($pattern, function($matches){

    $email = $matches[0];
    $encrypted_email = encrypt($email); // 暗号化
    return '<img src="/image/email/'. $encrypted_email .'">';

}, $email_text);

return view('email_image')
    ->with('email_text', $email_image_text);

まず、$patternはメールアドレスを探し出すパターン(正規表現)です。(なお、このパターンは、こちらのページRFC 5322 Official Standardとして紹介されているものです)

※ ちなみに100%メールアドレスにマッチする正規表現は存在しないと言われています。

そして、preg_replace_callback()の中では、取得したメールアドレスをLaravelのencrypt()で暗号化し、その文字列を含んだURLを画像のsrcとして使っています。

これで、HTMLは以下のようなものが表示されることになります。

<html>
<body>
<div>
    Example株式会社<br><img src="/image/email/(暗号化コード)">
</div>
</body>
</html>

メールアドレス画像をつくる

フォントを用意する

では、メールアドレスが書かれた画像を作成する部分ですが、その前にひとつだけ準備をしておく必要があります。それはフォントです。

Intervention Imageを使ってテキストを画像にすることはできますが、サイズを変更するなどしたい場合はフォントを指定しておくべきです。

そのため、商用でも利用可能なIPAフォントなどをダウンロードし、storage/fonts内に設置しておきましょう。(今回はipagp.ttfを使います)

テキストを画像化する

最初につくったImageController内にemail()を追加して画像が表示されるようにしましょう。

public function email($code) {

    $email = decrypt($code); // メールアドレスを復号

    \Image::configure(['driver' => 'imagick']); // ImageMagickを使う
    $width = 200;   // 画像の幅
    $height = 22;   // 画像の高さ
    $x = 5;
    $y = 5;
    $img = \Image::canvas($width, $height, '#ddd');
    $img->text($email, $x, $y, function($font) {
        $font->file(storage_path('app/fonts/ipagp.ttf')); // フォントを指定
        $font->size(18);      // テキストサイズ
        $font->align('left'); // 左揃え
        $font->valign('top'); // 上揃え
    });
    return $img->response();

}

やっていることは、まず$codeは暗号化されたメールアドレスなので、これをdecrypt()で元に戻します。

そして、より綺麗な画像が作成できるので、\Image::configure()でImageMagickを使う設定にしています。(ただし、ImageMagickを使うにはPHPのモジュールなどが必要になってくるため、めんどうな場合はここは削除しても問題ありません)

そして最後に、Intervention Imageで画像の中にテキストを描画してレスポンスを返しています。詳しくは、完全網羅!Intervention Image(PHP)で画像を編集する全実例を参考にしてください。

これでページにアクセスするとこのような画像が表示されます。(今回は分かりやすいように背景色をつけています)

では、先ほどつくった会社情報が表示されるページにもアクセスしてみましょう。

少し分かりにくいかもしれませんが、上の段は通常のテキストで、メールアドレスの部分だけ画像化されています。

おまけ・・・その1(ヘルパー関数化する)

今回つくったメールアドレスの画像化はサイトによってはとても汎用性が高い機能と言えます。そのため、この機能をヘルパー関数化してみましょう。

独自のヘルパー関数をつくるには、まずapp/helpers.phpを作成してemail_text()を定義します。

<?php

if (! function_exists('email_text')) {

    function email_text($text) {

        $pattern = '/(?:[a-z0-9!#$%&\'*+\/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&\'*+\/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])/';
        return preg_replace_callback($pattern, function($matches){

            $email = $matches[0];
            $encrypted_email = encrypt($email); // 暗号化
            return '<img src="/image/email/'. $encrypted_email .'">';

        }, $text);

    }

}

ただし、このままではコントローラーなどからはアクセスできませんので、composer.jsonautoload内に以下のように新しいヘルパー関数のファイルを登録します。

"autoload": {
    "files": [
        "app/helpers.php"
    ],

そして、以下のコマンドでautoloadを更新すればOKです。

composer dump-autoload

これで、どこからでもemail_text()とすればメールアドレスが画像化されたテキストを取得することができるようになりました。

おまけ・・・その2(画像を再利用するようにする)

現状では、ImageControlleremail()では毎回画像をつくるコードが実行されることになります。それほど大きな画像ではないのでコンピュータへの負荷は少ないかもしれませんが、何回も同じコードを実行するのはあまり賢いやり方とはいえません。

そこで、画像は最初に保存しておくことにし、もし存在していれば次からはその画像を読み込むという形にしてみましょう。

public function email($code) {

    $email = decrypt($code); // メールアドレスを復号
    $path = storage_path('app/images/email/'. md5($email) .'.jpg');

    if(file_exists($path)) { // 2回目以降はここを通る

        $img = \Image::make($path);

    } else {

        \Image::configure(['driver' => 'imagick']); // ImageMagickを使う
        $width = 200;   // 画像の幅
        $height = 22;   // 画像の高さ
        $x = 5;
        $y = 5;
        $img = \Image::canvas($width, $height, '#ddd');
        $img->text($email, $x, $y, function($font) {
            $font->file(storage_path('app/fonts/ipagp.ttf')); // フォントを指定
            $font->size(18);      // テキストサイズ
            $font->align('left'); // 左揃え
            $font->valign('top'); // 上揃え
        });
        $img->save($path);

    }

    return $img->response();

}

まず、$pathは暗号化されたコードだと少し長くなってしまうので、md5()でハッシュ化したファイル名を使っています。

そして、file_exists()でファイルの存在を確認し、

  • もし、なければ画像を作って保存する
  • もし、存在していればその画像を読み込む

という分岐をつくっています。

※ そのため、storage/app/images/emailというフォルダを作成し、書き込み権限をつけておいてください。

ソースコード一式をダウンロード

今回説明で使ったソースコード一式を以下からダウンロードすることができます。

※ ただし、Intervention Imageのインストール、ヘルパー関数のautoload、また画像作成のフォント設定はご自身で行う必要があります。

メールアドレスを画像化してスパムを防止: ソースコード一式