とりあえず「拡張アドレスの拡張部分を除去」が命題になるのですが…もうちょっと正しく書くとおそらくは違って、さらっとぶっちゃけると「DBでprimary keyだったりUNIQUE制約かかってたりするのにちゃんと引っかかる」状況を作り出したい、ってのが、おそらく本筋。
で…ここから少し面倒なお話し。
とりあえず標的に上がってるのは「+(プラス)」記号で、これが割と拡張アドレスとしてつかわれ「やすい」。
ただ、拡張アドレスはqmailだと「−(ハイフン)」だし、Postfixだと「設定によっては+も−も使える(Postfix 2.11以降)」。一方で「普通にlocal-partの文字として−(ハイフン)が使える」所もあるので、色々と悩ましい。
それ以外にも「quoted string(ダブルクォートで囲った文字列)をlocal-partにする」ってやり方もあってこれも割とあちこちで使えるし(斜め調査の限りでは、AUのスマホの「quoted stringアドレスへの送信」のみNGで、後は送受信とも割といける)。
あと、これはgmail以外では見ていないんだけど「Gmail ではユーザー名のピリオドを文字として認識しません( https://support.google.com/mail/answer/10313?hl=ja )」という謎仕様もあるので、ドット位置変えると割と色々いける(ドットの連続はどうもGoogle側で(も?)弾いてるぽい。「Final-Recipient: rfc822; "h..oge"@gmail.com」ってエラーがきた)。
この辺を考えると、細かくいくと「ドメイン毎に処理を変える」必要が出てきそうではあるんだけど。
大まかな「local-partからいらんものを引っぺがす!」ってのを前提に
・前後のダブルクォートは、あったら引っぺがす
・domain-partの文字は(小文字側に)揃える。local-partは「区別したりしなかったり」だと思うので、一旦「小文字に揃える」をデフォに。「小文字に揃えない(大文字と小文字を区別するMTA(RFC822準拠、RFC5321非準拠?)」の可能性が0ではないので、ギミックだけ作って運用対処としておく。
・+以降は「拡張アドレスの可能性が高い」ので、+以降を引っぺがす(後で「引っぺがさないドメインリスト」とか作って運用で足し込む)
・−以降は「拡張アドレスの可能性がある」ので、−以降を引っぺがす準備をする(「引っぺがすドメインリスト」とか作って運用で足し込む。初期は空でいいのかも)
・Googleアカウントについては「ドットを消す」:いるかなぁ? とも思うんだけど、可能なのでせっかくだしやっとく。これも「引っぺがすリスト」作って、後で追加対応にしておくといいのかなぁ?
こんな感じ。
ちなみに前提として「アドレスは到達する」事を期待しているので、ドメイン周りは未着手。
ドメイン周りに手を出したい場合、最終的には「DNS引くよねぇ」って話になるので、それはまた「やるならやって」的なところw
文章書きながらで実装3時間ちょい…うち1時間弱が「調査+書き物」、1時間が「コーディング」、1時間がテスト。
テストの大半は「テストデータの用意ミス」という切ないっぷり(苦笑
とりあえず「さほどコストかからずに書けるよ」って程度なので、一旦「プロトタイプ的なブツだけどこんなもんぢゃね?」程度のものを。
っつわけで、コード(+下の方に簡単なテスト付き)
<?php
// 拡張アドレスっぽいもの除去クラス
class extended_address_removal {
// 本当は「外部ファイルから読み込み」とかで適切に読み込んでくだせぇ(苦笑
public function __construct() {
//
$this->not_lower_list([
'not-lower-example.com',
]);
//
$this->plus_unremove_list([
'plus-unremove-example.com',
'hyphen-only-remove-example.com', // XXX ここ「二重登録」だなぁ…直感的じゃない…後で変えたほうがいいかも:これは「ハイフンのみ除去対象、+は生かす」設定用
]);
//
$this->hyphen_remove_list([
'hyphen-only-remove-example.com', // XXX ここ「二重登録」だなぁ…直感的じゃない…後で変えたほうがいいかも:これは「ハイフンのみ除去対象、+は生かす」設定用
'hyphen-remove-example.com', // こっちだと「ハイフンもプラスも除去する」になる
]);
//
$this->dot_remove_list([
'gmail.example.com',
]);
}
// 設定する系メソッド
// 検索しやすいようにarray_flipしてるのでほんのり注意
public function not_lower_list($awk) {
$this->not_lower_list_ = array_flip($awk);
}
public function plus_unremove_list($awk) {
$this->plus_unremove_list_ = array_flip($awk);
}
public function hyphen_remove_list($awk) {
$this->hyphen_remove_list_ = array_flip($awk);
}
public function dot_remove_list($awk) {
$this->dot_remove_list_ = array_flip($awk);
}
// 除去本体
public function remove($email) {
// local-partとdomain-partに切り分ける
$awk = explode('@', $email);
// 万が一「@無しの文字列」が入ってきた時用
// XXX このチェックいるか?
if (1 === count($awk)) {
return '';
}
//
$domain = array_pop($awk);
$local = implode('@', $awk);
//var_dump($local);
//var_dump($domain);
// domain-partの文字は(小文字側に)揃える。local-partは「区別したりしなかったり」だと思うので、一旦「小文字に揃える」をデフォに。「小文字に揃えない(大文字と小文字を区別するMTA(RFC822準拠、RFC5321非準拠?)」の可能性が0ではないので、ギミックだけ作って運用対処としておく。
if (false === isset($this->not_lower_list_[$domain])) {
$local = strtolower($local);
}
// 前後のダブルクォートは、あったら引っぺがす
// XXX 不完全なquoted stringは一旦弾こう
$flg = ('"' === $local[0]); // 先頭の"チェック
$flg2 = ('"' === $local[strlen($local) - 1]); // 最後の"チェック
// 「どっちかだけtrue」は不完全なquoted stringなのでエラー
if ($flg xor $flg2) {
return '';
}
// quoted stringなら除去処理
if (true === $flg) {
$local = substr($local, 1, -1);
}
//var_dump($local);
//var_dump($domain);
// ・+以降は「拡張アドレスの可能性が高い」ので、+以降を引っぺがす(後で「引っぺがさないドメインリスト」とか作って運用で足し込む)
if (false === isset($this->plus_unremove_list_[$domain])) {
$awk = explode('+', $local);
$local = $awk[0];
}
// ・−以降は「拡張アドレスの可能性がある」ので、−以降を引っぺがす準備をする(「引っぺがすドメインリスト」とか作って運用で足し込む。初期は空でいいのかも)
if (true === isset($this->hyphen_remove_list_[$domain])) {
$awk = explode('-', $local);
$local = $awk[0];
}
// ・Googleアカウントについては「ドットを消す」:いるかなぁ? とも思うんだけど、可能なのでせっかくだしやっとく。これも「引っぺがすリスト」作って、後で追加対応にしておくといいのかなぁ?
if (true === isset($this->dot_remove_list_[$domain])) {
$local = str_replace('.', '', $local);
}
//
return $local . '@' . $domain;
}
//private:
private $not_lower_list_;
private $plus_unremove_list_;
private $hyphen_remove_list_;
private $dot_remove_list_;
} // end of class
/*
* 以下テスト
*/
// テスト用アドレス群
$add = [
// ノーマルなパターン
'nobody@example.com' => 'nobody@example.com',
'NObody@example.com' => 'nobody@example.com', // local-partは小文字に揃える
'no+body@example.com' => 'no@example.com', // 拡張なので除去
'no-body@example.com' => 'no-body@example.com', // ハイフンはデフォでは除去しない
'no.body@example.com' => 'no.body@example.com', // .(dot)はデフォでは除去しない
'"no+body"@example.com' => 'no@example.com', // (quoted string)拡張なので除去
'"no-body"@example.com' => 'no-body@example.com', // (quoted string)ハイフンはデフォでは除去しない
'"no.body"@example.com' => 'no.body@example.com', // (quoted string) .(dot)はデフォでは除去しない
'"no.body@example.com' => '', // 不完全なquoted string
'no.body"@example.com' => '', // 不完全なquoted string
'"nob@ody"@example.com' => 'nob@ody@example.com', // quoted stringの除去
// local-partを小文字にしない
'NObody@not-lower-example.com' => 'NObody@not-lower-example.com',
// プラスを除去しない
'no+body@plus-unremove-example.com' => 'no+body@plus-unremove-example.com',
'"no+body"@plus-unremove-example.com' => 'no+body@plus-unremove-example.com',
// ハイフンを取り除き、プラスは除去しない
'no-body@hyphen-only-remove-example.com' => 'no@hyphen-only-remove-example.com',
'"no-body"@hyphen-only-remove-example.com' => 'no@hyphen-only-remove-example.com',
'n+o-body@hyphen-only-remove-example.com' => 'n+o@hyphen-only-remove-example.com',
'"n+o-body"@hyphen-only-remove-example.com' => 'n+o@hyphen-only-remove-example.com',
// ハイフンもプラスも取り除く
'no-body@hyphen-remove-example.com' => 'no@hyphen-remove-example.com',
'"no-body"@hyphen-remove-example.com' => 'no@hyphen-remove-example.com',
'n+o-body@hyphen-remove-example.com' => 'n@hyphen-remove-example.com',
'"n+o-body"@hyphen-remove-example.com' => 'n@hyphen-remove-example.com',
// dotを取り除く
'no.b.ody@gmail.example.com' => 'nobody@gmail.example.com',
'no.b.ody+test@gmail.example.com' => 'nobody@gmail.example.com',
'"no.b.ody"@gmail.example.com' => 'nobody@gmail.example.com',
'"no.b.ody+test"@gmail.example.com' => 'nobody@gmail.example.com',
];
//
$error_count = 0;
$obj = new extended_address_removal();
foreach($add as $email => $expectation_email) {
$r = $obj->remove($email);
if ($r === $expectation_email) {
//echo "ok\n";
echo "ok: {$email} => {$r}\n";
} else {
echo "ng orz\n";
//echo "{$email} => {$r} !: {$expectation_email}\n";
$error_count ++;
}
}
if (0 === $error_count) {
echo "...................ALL OK!!\n";
} else {
echo "................NG( {$error_count} )!!\n";
}とりあえずこのクラス使うと「なんとなし拡張っぽいものを一通り引っぺがせる」ので。
実装するemailアドレスは別カラムにちゃんと保存しておいてもらうとして、こいつで引っ張ってきたアドレスをUNIQUE制約したりしておくと、当初目的である
・拡張アドレスは認める
・重複したアドレスは認めない
が、ある程度いけるんではないかなぁ、と。
あとは運用でドメインを追加したりすれば、まぁまぁいけるんじゃないかなぁ、って思うざます。
…多分、そのうちMagicWeaponに追加するような気がするw