要旨
ドメイン名のしくみ - JPNIC を読む機会がありまして、ドメイン名の正規表現を書いてみたくなって書いてしまいました。この記事では、作った正規表現の解説をメインに説明します。 PHP
の PCRE
正規表現で書きます。
書いた正規表現の答え合わせをしようと思って、「ドメイン 正規表現」でググったのですが、上位に出てきた10件のページはみんな間違っていました。
もしかしたらもっと下の方には正しい正規表現を説明したページがあるのかもしれません。しかし、間違ったページが上に来るのはよろしくないですね。この記事が正しいと思った方は、この記事が「ドメイン 正規表現」のトップになるように協力してくださいmm
要件
上記のページにはこのような記載があります。この記事ではこの記載をもとに要件を定義します。
ピリオド(.)で区切られた部分は「ラベル」と呼ばれます。 一つのラベルの長さは63文字以下、ドメイン名全体の長さは、 ピリオドを含めて253文字以下でなければなりません。※1 ラベルには、英字(A~Z)、数字(0~9)、 ハイフン( - )が使用できます(ラベルの先頭と末尾の文字をハイフンとするのは不可)。 ラベル中では大文字・小文字の区別はなく、 同じ文字とみなされます。
以下を要件とします ↓
- ラベルとは、ハイフン(-)、数字(0-9)、アルファベット大文字(A-Z)、アルファベット小文字(a-z)のみからなる文字列である ※1
- ラベルの長さは1文字以上、63文字以下である ※2
- ラベルの先頭と末尾の文字はハイフンではない
- ドメイン名とは、1つ以上のラベルの列をピリオド(.)で繋げた文字列である
- ドメイン名の長さには制限がないものとする ※3
- ※1: 大文字・小文字の区別はない、と書かれているので、アルファベット大文字(A-Z)に加えてアルファベット小文字(a-z)を含めています。
- ※2: 最低文字数に言及されていないので、勝手に1文字以上にしておきました。本質的ではないと考えているので適当です。
- ※3: ドメイン名の長さは253文字以下、と書かれていますが、そんなのはあとで
strlen()
でもすればいいので本質的ではないと考えて無視しました。
解答
$regexp = '/^(?!\-)[\-0-9A-Za-z]{1,63}(?<!\-)(?:\.(?!\-)[\-0-9A-Za-z]{1,63}(?<!\-))*$/';
解説
PHP: PCRE 正規表現構文 - Manual はとても充実しています。なので、わざわざまとめ直すことはせず、長部の解説を読むのに必要なドキュメントのリンクを貼るようにします。
ラベルの正規表現
Step 1:
1. ラベルとは、ハイフン(-)、数字(0-9)、アルファベット大文字(A-Z)、アルファベット小文字(a-z)のみからなる文字列である ※1
を実装すると
$regexp = '/[\-0-9A-Za-z]*/';
参考文献
Step 2:
2. ラベルの長さは1文字以上、63文字以下である ※2
を加えて実装すると
$regexp = '/[\-0-9A-Za-z]{1,63}/';
参考文献
Step 3:
3. ラベルの先頭と末尾の文字はハイフンではない
をさらに加えて実装すると
$regexp = '/(?!\-)[\-0-9A-Za-z]{1,63}(?<!\-)/';
参考文献
ドメイン名の正規表現
見やすさのため、一旦 LABEL = (?!\-)[\-0-9A-Za-z]{1,63}(?<!\-)
と置くことにします。これは、 LABEL
が出てきたら (?!\-)[\-0-9A-Za-z]{1,63}(?<!\-)
で置換する、ということを意味すると約束します。マクロの感覚で導入しました。
4. ドメイン名とは、1つ以上のラベルの列をピリオド(.)で繋げた文字列である
を実装すると
// PCRE ではマクロは使えないので、そのままでは動きません
// キャプチャしても使わないものはキャプチャしない主義なので (?:) を使っています
$regexp = '/LABEL(?:\.LABEL)*/';
マクロを展開し、始まりと終わりの言明もつけると
$regexp = '/^(?!\-)[\-0-9A-Za-z]{1,63}(?<!\-)(?:\.(?!\-)[\-0-9A-Za-z]{1,63}(?<!\-))*$/';
参考文献
おわりに
他のページが間違っているんだ!と言ってこの記事を書きましたが、そもそもこの記事も間違っているかもしれません。もしくは、間違っていないにしても、本質的なところを本質的でないと言って切り捨てているかもしれません。変なところに気づいた方は、コメント欄にてボコボコにして頂けると幸いです。