「お客さんにメールが届かないのでどうかして?」という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ではその不正を許す必要あります。まだ使っている人いるんですよ。
メルマガ購読の申し込みはこちらから。