「お客さんにメールが届かないのでどうかして?」というCSからの質問。見てみるとexample@yahoo.comであるところ、example@yahpo.comになっていたりします。たった1文字違いですが、会員登録してもこのためにメールが届かないばかりか、次回からは多分ログインもできません。もちろんお客さんの間違いですが、登録時にこれが通ってしまうというのは問題です。これをどうかしたいです。
緩いメールアドレスのバリデーション
Laravelのデフォルトのメールアドレスのバリデーションemailは、結構緩いです。どれだけ緩いが実例見てみましょう。以下は、tinkerを使って、test@gmailという不正なメールアドレスチェックします。
>>> $v = validator(['email' => 'test@gmail'], ['email' => 'required|email']) => Illuminate\Validation\Validator {#3534 +customMessages: [], +fallbackMessages: [], +customAttributes: [], +customValues: [], +excludeUnvalidatedArrayKeys: null, +extensions: [], +replacers: [], } >>> $v->passes() => true
あっさり、通ってしまいます。
また、example.comとかのドメイン名は実在しないのに、これも通ります。
>>> $v = validator(['email' => 'test@example.com'], ['email' => 'required|email']) ... >>> $v->passes() => true
email:dnsの登場
メールアドレスの@マークの後ろの部分、例えば、test@example.comなら、example.comの部分、はドメイン名と言ってインターネットでは非常に重要なデータなのですが、このドメインが実在するかどうか、そしてメールを受け取るかどうかがわかれば、少なくともメールアドレスのタイポは登録時に警告できますね。
それを利用したのが、email:dnsのバリデーションルールです。早速、tinkerで試してみましょう。
>>> $v = validator(['email' => 'test@gmail'], ['email' => 'required|email:dns']) ... >>> $v->passes() => false >>> $v = validator(['email' => 'test@example.com'], ['email' => 'required|email:dns']) ... >>> $v->passes() => false >>> $v = validator(['email' => 'test@yahpo.com'], ['email' => 'required|email:dns']) ... >>> $v->passes() => false
素晴らしいです、みな通りません!
email:dnsの使用において注意が必要なのは、それを利用するには、以下の国際化関数、intlのライブラリが必要です。
https://www.php.net/manual/ja/book.intl.php
dns_get_record()
ちょっとemail:dnsの仕組みを探ってみましょう。Laravelのマニュアルによると、egulias/email-validatorのパッケージを使用しているそうな。そこから関心のコードの一部を掲載します。
... class DNSCheckValidation implements EmailValidation { /** * @var int */ protected const DNS_RECORD_TYPES_TO_CHECK = DNS_MX + DNS_A + DNS_AAAA; /** * Reserved Top Level DNS Names (https://tools.ietf.org/html/rfc2606#section-2), * mDNS and private DNS Namespaces (https://tools.ietf.org/html/rfc6762#appendix-G) */ const RESERVED_DNS_TOP_LEVEL_NAMES = [ //これらのドメイン名はみなエラーとなります // Reserved Top Level DNS Names 'test', 'example', 'invalid', 'localhost', // mDNS 'local', // Private DNS Namespaces 'intranet', 'internal', 'private', 'corp', 'home', 'lan', ]; ... private function validateDnsRecords($host) : bool { // A workaround to fix https://bugs.php.net/bug.php?id=73149 /** @psalm-suppress InvalidArgument */ set_error_handler( static function (int $errorLevel, string $errorMessage): ?bool { throw new \RuntimeException("Unable to get DNS record for the host: $errorMessage"); } ); try { // Get all MX, A and AAAA DNS records for host $dnsRecords = dns_get_record($host, static::DNS_RECORD_TYPES_TO_CHECK); } catch (\RuntimeException $exception) { $this->error = new InvalidEmail(new UnableToGetDNSRecord(), ''); return false; } finally { restore_error_handler(); } ...
上の45行目見てください。dns_get_record()
なるphp関数があったのですね。
https://www.php.net/manual/ja/function.dns-get-record.php
上の説明によると、こんなしてドメイン情報取得できるようです。
>>> dns_get_record("yahpo.com", DNS_MX); => [ [ "host" => "yahpo.com", "class" => "IN", "ttl" => 0, "type" => "MX", "pri" => 0, "target" => "", ], ] >>> dns_get_record("yahoo.com", DNS_MX); => [ [ "host" => "yahoo.com", "class" => "IN", "ttl" => 0, "type" => "MX", "pri" => 1, "target" => "mta5.am0.yahoodns.net", ], [ "host" => "yahoo.com", "class" => "IN", "ttl" => 0, "type" => "MX", "pri" => 1, "target" => "mta6.am0.yahoodns.net", ], [ "host" => "yahoo.com", "class" => "IN", "ttl" => 0, "type" => "MX", "pri" => 1, "target" => "mta7.am0.yahoodns.net", ], ]
最初のタイポのドメイン名のtargetは存在しないけれど、正式なドメインは3つtargetを返しています。Laravelのemail:dnsはこれを利用しているのですね。
最後に
メールのバリデーションには、email:dnsの他にもrfcとかstrictとかspoofとかあります。
emailとemail:rfcは同じです。また、email:rfc,dnsとように合わせての使用も可能です。
あと大事なのは、バリデーションでは必ず弾かれてしまうが、test.@docomo.ne.jpのようにドットが不正な場所にあるにもかかわらず実在するメールアドレスの対応です。docomo.ne.jpやezweb.ne.jpではその不正を許す必要あります。まだ使っている人いるんですよ。
メルマガ購読の申し込みはこちらから。