Jawaban cepat
Gunakan regex berikut untuk validasi input:
([-!#-'*+/-9=?A-Z^-~]+(\.[-!#-'*+/-9=?A-Z^-~]+)*|"([]!#-[^-~ \t]|(\\[\t -~]))+")@[0-9A-Za-z]([0-9A-Za-z-]{0,61}[0-9A-Za-z])?(\.[0-9A-Za-z]([0-9A-Za-z-]{0,61}[0-9A-Za-z])?)+
Alamat yang cocok dengan regex ini:
- memiliki bagian lokal (yaitu bagian sebelum tanda-@) yang sepenuhnya sesuai dengan RFC 5321/5322,
- memiliki bagian domain (yaitu bagian setelah tanda-@) yang merupakan nama host dengan setidaknya dua label, yang masing-masing memiliki paling banyak 63 karakter.
Kendala kedua adalah pembatasan pada RFC 5321/5322.
Jawaban yang rumit
Menggunakan ekspresi reguler yang mengenali alamat email dapat berguna dalam berbagai situasi: misalnya untuk memindai alamat email dalam dokumen, untuk memvalidasi input pengguna, atau sebagai batasan integritas pada repositori data.
Namun perlu dicatat bahwa jika Anda ingin mengetahui apakah alamat tersebut benar-benar merujuk ke kotak surat yang ada, tidak ada pengganti untuk mengirim pesan ke alamat tersebut. Jika Anda hanya ingin memeriksa apakah suatu alamat secara tata bahasa benar maka Anda dapat menggunakan ekspresi reguler, tetapi perhatikan bahwa itu ""@[]
adalah alamat email yang benar secara tata bahasa yang tentu saja tidak merujuk ke kotak surat yang ada.
Sintaks alamat email telah didefinisikan dalam berbagai RFC , terutama RFC 822 dan RFC 5322 . RFC 822 harus dilihat sebagai standar "asli" dan RFC 5322 sebagai standar terbaru. Sintaks yang didefinisikan dalam RFC 822 adalah standar yang paling lunak dan selanjutnya telah membatasi sintaks lebih lanjut dan lebih jauh, di mana sistem atau layanan yang lebih baru harus mengenali sintaks yang usang, tetapi tidak pernah memproduksinya.
Dalam jawaban ini saya akan mengambil "alamat email" untuk berarti addr-spec
sebagaimana didefinisikan dalam RFC (yaitu jdoe@example.org
, tetapi tidak "John Doe"<jdoe@example.org>
, tidak juga some-group:jdoe@example.org,mrx@exampel.org;
).
Ada satu masalah dengan menerjemahkan sintaks RFC ke dalam regex: sintaksinya tidak teratur! Ini karena mereka memungkinkan untuk komentar opsional di alamat email yang dapat disarangkan tanpa batas, sementara bersarang tanpa batas tidak dapat dijelaskan dengan ekspresi reguler. Untuk memindai atau memvalidasi alamat yang berisi komentar, Anda memerlukan parser atau ekspresi yang lebih kuat. (Perhatikan bahwa bahasa seperti Perl memiliki konstruksi untuk menggambarkan tata bahasa bebas konteks dengan cara seperti regex.) Dalam jawaban ini saya akan mengabaikan komentar dan hanya mempertimbangkan ekspresi reguler yang tepat.
RFC menentukan sintaksis untuk pesan email, bukan untuk alamat email. Alamat dapat muncul di berbagai bidang tajuk dan ini adalah tempat mereka ditentukan. Ketika mereka muncul di alamat bidang header mungkin berisi (antara token leksikal) spasi putih, komentar dan bahkan linebreak. Namun secara semantik ini tidak memiliki signifikansi. Dengan menghapus spasi putih ini, dll dari alamat, Anda mendapatkan representasi kanonik yang secara semantik setara . Dengan demikian, representasi kanonik first. last (comment) @ [3.5.7.9]
adalah first.last@[3.5.7.9]
.
Sintaks yang berbeda harus digunakan untuk tujuan yang berbeda. Jika Anda ingin memindai alamat email dalam dokumen (mungkin sangat lama) mungkin ide yang baik untuk menggunakan sintaksis seperti yang didefinisikan dalam RFC 822. Di sisi lain, jika Anda ingin memvalidasi input pengguna Anda mungkin ingin menggunakan sintaksis sebagaimana didefinisikan dalam RFC 5322, mungkin hanya menerima representasi kanonik. Anda harus memutuskan sintaksis mana yang berlaku untuk kasus spesifik Anda.
Saya menggunakan ekspresi reguler POSIX "extended" dalam jawaban ini, dengan asumsi rangkaian karakter yang kompatibel ASCII.
RFC 822
Saya tiba di ekspresi reguler berikut. Saya mengundang semua orang untuk mencoba dan memecahkannya. Jika Anda menemukan kesalahan positif atau negatif palsu, kirimkan dalam komentar dan saya akan mencoba untuk memperbaiki ekspresi sesegera mungkin.
([^][()<>@,;:\\". \x00-\x1F\x7F]+|"(\n|(\\\r)*([^"\\\r\n]|\\[^\r]))*(\\\r)*")(\.([^][()<>@,;:\\". \x00-\x1F\x7F]+|"(\n|(\\\r)*([^"\\\r\n]|\\[^\r]))*(\\\r)*"))*@([^][()<>@,;:\\". \x00-\x1F\x7F]+|\[(\n|(\\\r)*([^][\\\r\n]|\\[^\r]))*(\\\r)*])(\.([^][()<>@,;:\\". \x00-\x1F\x7F]+|\[(\n|(\\\r)*([^][\\\r\n]|\\[^\r]))*(\\\r)*]))*
Saya percaya ini sepenuhnya sesuai dengan RFC 822 termasuk errata . Itu hanya mengenali alamat email dalam bentuk kanonik mereka. Untuk regex yang mengenali spasi (melipat) lihat derivasi di bawah ini.
Derivasi menunjukkan bagaimana saya sampai pada ekspresi. Saya mencantumkan semua aturan tata bahasa yang relevan dari RFC persis seperti yang muncul, diikuti oleh regex yang sesuai. Di mana erratum telah diterbitkan, saya memberikan ekspresi terpisah untuk aturan tata bahasa yang diperbaiki (ditandai "erratum") dan menggunakan versi yang diperbarui sebagai subekspresi dalam ekspresi reguler berikutnya.
Sebagaimana dinyatakan dalam paragraf 3.1.4. RFC 822 spasi putih linier opsional dapat dimasukkan di antara token leksikal. Jika berlaku, saya telah memperluas ekspresi untuk mengakomodasi aturan ini dan menandai hasilnya dengan "opt-lwsp".
CHAR = <any ASCII character>
=~ .
CTL = <any ASCII control character and DEL>
=~ [\x00-\x1F\x7F]
CR = <ASCII CR, carriage return>
=~ \r
LF = <ASCII LF, linefeed>
=~ \n
SPACE = <ASCII SP, space>
=~
HTAB = <ASCII HT, horizontal-tab>
=~ \t
<"> = <ASCII quote mark>
=~ "
CRLF = CR LF
=~ \r\n
LWSP-char = SPACE / HTAB
=~ [ \t]
linear-white-space = 1*([CRLF] LWSP-char)
=~ ((\r\n)?[ \t])+
specials = "(" / ")" / "<" / ">" / "@" / "," / ";" / ":" / "\" / <"> / "." / "[" / "]"
=~ [][()<>@,;:\\".]
quoted-pair = "\" CHAR
=~ \\.
qtext = <any CHAR excepting <">, "\" & CR, and including linear-white-space>
=~ [^"\\\r]|((\r\n)?[ \t])+
dtext = <any CHAR excluding "[", "]", "\" & CR, & including linear-white-space>
=~ [^][\\\r]|((\r\n)?[ \t])+
quoted-string = <"> *(qtext|quoted-pair) <">
=~ "([^"\\\r]|((\r\n)?[ \t])|\\.)*"
(erratum) =~ "(\n|(\\\r)*([^"\\\r\n]|\\[^\r]|(\r\n)?[ \t]))*(\\\r)*"
domain-literal = "[" *(dtext|quoted-pair) "]"
=~ \[([^][\\\r]|((\r\n)?[ \t])|\\.)*]
(erratum) =~ \[(\n|(\\\r)*([^][\\\r\n]|\\[^\r]|(\r\n)?[ \t]))*(\\\r)*]
atom = 1*<any CHAR except specials, SPACE and CTLs>
=~ [^][()<>@,;:\\". \x00-\x1F\x7F]+
word = atom / quoted-string
=~ [^][()<>@,;:\\". \x00-\x1F\x7F]+|"(\n|(\\\r)*([^"\\\r\n]|\\[^\r]|(\r\n)?[ \t]))*(\\\r)*"
domain-ref = atom
sub-domain = domain-ref / domain-literal
=~ [^][()<>@,;:\\". \x00-\x1F\x7F]+|\[(\n|(\\\r)*([^][\\\r\n]|\\[^\r]|(\r\n)?[ \t]))*(\\\r)*]
local-part = word *("." word)
=~ ([^][()<>@,;:\\". \x00-\x1F\x7F]+|"(\n|(\\\r)*([^"\\\r\n]|\\[^\r]|(\r\n)?[ \t]))*(\\\r)*")(\.([^][()<>@,;:\\". \x00-\x1F\x7F]+|"(\n|(\\\r)*([^"\\\r\n]|\\[^\r]|(\r\n)?[ \t]))*(\\\r)*"))*
(opt-lwsp) =~ ([^][()<>@,;:\\". \x00-\x1F\x7F]+|"(\n|(\\\r)*([^"\\\r\n]|\\[^\r]|(\r\n)?[ \t]))*(\\\r)*")(((\r\n)?[ \t])*\.((\r\n)?[ \t])*([^][()<>@,;:\\". \x00-\x1F\x7F]+|"(\n|(\\\r)*([^"\\\r\n]|\\[^\r]|(\r\n)?[ \t]))*(\\\r)*"))*
domain = sub-domain *("." sub-domain)
=~ ([^][()<>@,;:\\". \x00-\x1F\x7F]+|\[(\n|(\\\r)*([^][\\\r\n]|\\[^\r]|(\r\n)?[ \t]))*(\\\r)*])(\.([^][()<>@,;:\\". \x00-\x1F\x7F]+|\[(\n|(\\\r)*([^][\\\r\n]|\\[^\r]|(\r\n)?[ \t]))*(\\\r)*]))*
(opt-lwsp) =~ ([^][()<>@,;:\\". \x00-\x1F\x7F]+|\[(\n|(\\\r)*([^][\\\r\n]|\\[^\r]|(\r\n)?[ \t]))*(\\\r)*])(((\r\n)?[ \t])*\.((\r\n)?[ \t])*([^][()<>@,;:\\". \x00-\x1F\x7F]+|\[(\n|(\\\r)*([^][\\\r\n]|\\[^\r]|(\r\n)?[ \t]))*(\\\r)*]))*
addr-spec = local-part "@" domain
=~ ([^][()<>@,;:\\". \x00-\x1F\x7F]+|"(\n|(\\\r)*([^"\\\r\n]|\\[^\r]|(\r\n)?[ \t]))*(\\\r)*")(\.([^][()<>@,;:\\". \x00-\x1F\x7F]+|"(\n|(\\\r)*([^"\\\r\n]|\\[^\r]|(\r\n)?[ \t]))*(\\\r)*"))*@([^][()<>@,;:\\". \x00-\x1F\x7F]+|\[(\n|(\\\r)*([^][\\\r\n]|\\[^\r]|(\r\n)?[ \t]))*(\\\r)*])(\.([^][()<>@,;:\\". \x00-\x1F\x7F]+|\[(\n|(\\\r)*([^][\\\r\n]|\\[^\r]|(\r\n)?[ \t]))*(\\\r)*]))*
(opt-lwsp) =~ ([^][()<>@,;:\\". \x00-\x1F\x7F]+|"(\n|(\\\r)*([^"\\\r\n]|\\[^\r]|(\r\n)?[ \t]))*(\\\r)*")((\r\n)?[ \t])*(\.((\r\n)?[ \t])*([^][()<>@,;:\\". \x00-\x1F\x7F]+|"(\n|(\\\r)*([^"\\\r\n]|\\[^\r]|(\r\n)?[ \t]))*(\\\r)*")((\r\n)?[ \t])*)*@((\r\n)?[ \t])*([^][()<>@,;:\\". \x00-\x1F\x7F]+|\[(\n|(\\\r)*([^][\\\r\n]|\\[^\r]|(\r\n)?[ \t]))*(\\\r)*])(((\r\n)?[ \t])*\.((\r\n)?[ \t])*([^][()<>@,;:\\". \x00-\x1F\x7F]+|\[(\n|(\\\r)*([^][\\\r\n]|\\[^\r]|(\r\n)?[ \t]))*(\\\r)*]))*
(canonical) =~ ([^][()<>@,;:\\". \x00-\x1F\x7F]+|"(\n|(\\\r)*([^"\\\r\n]|\\[^\r]))*(\\\r)*")(\.([^][()<>@,;:\\". \x00-\x1F\x7F]+|"(\n|(\\\r)*([^"\\\r\n]|\\[^\r]))*(\\\r)*"))*@([^][()<>@,;:\\". \x00-\x1F\x7F]+|\[(\n|(\\\r)*([^][\\\r\n]|\\[^\r]))*(\\\r)*])(\.([^][()<>@,;:\\". \x00-\x1F\x7F]+|\[(\n|(\\\r)*([^][\\\r\n]|\\[^\r]))*(\\\r)*]))*
RFC 5322
Saya tiba di ekspresi reguler berikut. Saya mengundang semua orang untuk mencoba dan memecahkannya. Jika Anda menemukan kesalahan positif atau negatif palsu, kirimkan dalam komentar dan saya akan mencoba untuk memperbaiki ekspresi sesegera mungkin.
([-!#-'*+/-9=?A-Z^-~]+(\.[-!#-'*+/-9=?A-Z^-~]+)*|"([]!#-[^-~ \t]|(\\[\t -~]))+")@([-!#-'*+/-9=?A-Z^-~]+(\.[-!#-'*+/-9=?A-Z^-~]+)*|\[[\t -Z^-~]*])
Saya percaya ini sepenuhnya sesuai dengan RFC 5322 termasuk errata . Itu hanya mengenali alamat email dalam bentuk kanonik mereka. Untuk regex yang mengenali spasi (melipat) lihat derivasi di bawah ini.
Derivasi menunjukkan bagaimana saya sampai pada ekspresi. Saya mencantumkan semua aturan tata bahasa yang relevan dari RFC persis seperti yang muncul, diikuti oleh regex yang sesuai. Untuk aturan yang menyertakan spasi putih yang tidak relevan (melipat), saya memberikan regex terpisah bertanda "(dinormalisasi)" yang tidak menerima spasi putih ini.
Saya mengabaikan semua aturan "obs-" dari RFC. Ini berarti bahwa regex hanya cocok dengan alamat email yang sepenuhnya sesuai dengan RFC 5322. Jika Anda harus mencocokkan alamat "lama" (seperti tata bahasa yang lebih longgar termasuk aturan "obs-"), Anda dapat menggunakan salah satu dari regex RFC 822 dari paragraf sebelumnya.
VCHAR = %x21-7E
=~ [!-~]
ALPHA = %x41-5A / %x61-7A
=~ [A-Za-z]
DIGIT = %x30-39
=~ [0-9]
HTAB = %x09
=~ \t
CR = %x0D
=~ \r
LF = %x0A
=~ \n
SP = %x20
=~
DQUOTE = %x22
=~ "
CRLF = CR LF
=~ \r\n
WSP = SP / HTAB
=~ [\t ]
quoted-pair = "\" (VCHAR / WSP)
=~ \\[\t -~]
FWS = ([*WSP CRLF] 1*WSP)
=~ ([\t ]*\r\n)?[\t ]+
ctext = %d33-39 / %d42-91 / %d93-126
=~ []!-'*-[^-~]
("comment" is left out in the regex)
ccontent = ctext / quoted-pair / comment
=~ []!-'*-[^-~]|(\\[\t -~])
(not regular)
comment = "(" *([FWS] ccontent) [FWS] ")"
(is equivalent to FWS when leaving out comments)
CFWS = (1*([FWS] comment) [FWS]) / FWS
=~ ([\t ]*\r\n)?[\t ]+
atext = ALPHA / DIGIT / "!" / "#" / "$" / "%" / "&" / "'" / "*" / "+" / "-" / "/" / "=" / "?" / "^" / "_" / "`" / "{" / "|" / "}" / "~"
=~ [-!#-'*+/-9=?A-Z^-~]
dot-atom-text = 1*atext *("." 1*atext)
=~ [-!#-'*+/-9=?A-Z^-~]+(\.[-!#-'*+/-9=?A-Z^-~]+)*
dot-atom = [CFWS] dot-atom-text [CFWS]
=~ (([\t ]*\r\n)?[\t ]+)?[-!#-'*+/-9=?A-Z^-~]+(\.[-!#-'*+/-9=?A-Z^-~]+)*(([\t ]*\r\n)?[\t ]+)?
(normalized) =~ [-!#-'*+/-9=?A-Z^-~]+(\.[-!#-'*+/-9=?A-Z^-~]+)*
qtext = %d33 / %d35-91 / %d93-126
=~ []!#-[^-~]
qcontent = qtext / quoted-pair
=~ []!#-[^-~]|(\\[\t -~])
(erratum)
quoted-string = [CFWS] DQUOTE ((1*([FWS] qcontent) [FWS]) / FWS) DQUOTE [CFWS]
=~ (([\t ]*\r\n)?[\t ]+)?"(((([\t ]*\r\n)?[\t ]+)?([]!#-[^-~]|(\\[\t -~])))+(([\t ]*\r\n)?[\t ]+)?|(([\t ]*\r\n)?[\t ]+)?)"(([\t ]*\r\n)?[\t ]+)?
(normalized) =~ "([]!#-[^-~ \t]|(\\[\t -~]))+"
dtext = %d33-90 / %d94-126
=~ [!-Z^-~]
domain-literal = [CFWS] "[" *([FWS] dtext) [FWS] "]" [CFWS]
=~ (([\t ]*\r\n)?[\t ]+)?\[((([\t ]*\r\n)?[\t ]+)?[!-Z^-~])*(([\t ]*\r\n)?[\t ]+)?](([\t ]*\r\n)?[\t ]+)?
(normalized) =~ \[[\t -Z^-~]*]
local-part = dot-atom / quoted-string
=~ (([\t ]*\r\n)?[\t ]+)?[-!#-'*+/-9=?A-Z^-~]+(\.[-!#-'*+/-9=?A-Z^-~]+)*(([\t ]*\r\n)?[\t ]+)?|(([\t ]*\r\n)?[\t ]+)?"(((([\t ]*\r\n)?[\t ]+)?([]!#-[^-~]|(\\[\t -~])))+(([\t ]*\r\n)?[\t ]+)?|(([\t ]*\r\n)?[\t ]+)?)"(([\t ]*\r\n)?[\t ]+)?
(normalized) =~ [-!#-'*+/-9=?A-Z^-~]+(\.[-!#-'*+/-9=?A-Z^-~]+)*|"([]!#-[^-~ \t]|(\\[\t -~]))+"
domain = dot-atom / domain-literal
=~ (([\t ]*\r\n)?[\t ]+)?[-!#-'*+/-9=?A-Z^-~]+(\.[-!#-'*+/-9=?A-Z^-~]+)*(([\t ]*\r\n)?[\t ]+)?|(([\t ]*\r\n)?[\t ]+)?\[((([\t ]*\r\n)?[\t ]+)?[!-Z^-~])*(([\t ]*\r\n)?[\t ]+)?](([\t ]*\r\n)?[\t ]+)?
(normalized) =~ [-!#-'*+/-9=?A-Z^-~]+(\.[-!#-'*+/-9=?A-Z^-~]+)*|\[[\t -Z^-~]*]
addr-spec = local-part "@" domain
=~ ((([\t ]*\r\n)?[\t ]+)?[-!#-'*+/-9=?A-Z^-~]+(\.[-!#-'*+/-9=?A-Z^-~]+)*(([\t ]*\r\n)?[\t ]+)?|(([\t ]*\r\n)?[\t ]+)?"(((([\t ]*\r\n)?[\t ]+)?([]!#-[^-~]|(\\[\t -~])))+(([\t ]*\r\n)?[\t ]+)?|(([\t ]*\r\n)?[\t ]+)?)"(([\t ]*\r\n)?[\t ]+)?)@((([\t ]*\r\n)?[\t ]+)?[-!#-'*+/-9=?A-Z^-~]+(\.[-!#-'*+/-9=?A-Z^-~]+)*(([\t ]*\r\n)?[\t ]+)?|(([\t ]*\r\n)?[\t ]+)?\[((([\t ]*\r\n)?[\t ]+)?[!-Z^-~])*(([\t ]*\r\n)?[\t ]+)?](([\t ]*\r\n)?[\t ]+)?)
(normalized) =~ ([-!#-'*+/-9=?A-Z^-~]+(\.[-!#-'*+/-9=?A-Z^-~]+)*|"([]!#-[^-~ \t]|(\\[\t -~]))+")@([-!#-'*+/-9=?A-Z^-~]+(\.[-!#-'*+/-9=?A-Z^-~]+)*|\[[\t -Z^-~]*])
Perhatikan bahwa beberapa sumber (terutama w3c ) mengklaim bahwa RFC 5322 terlalu ketat pada bagian lokal (yaitu bagian sebelum tanda-@). Ini karena "..", "a..b" dan "a." yang tidak valid dot-atom, sementara mereka dapat digunakan sebagai nama kotak surat. RFC, bagaimanapun, tidak memungkinkan untuk bagian lokal seperti ini, kecuali bahwa mereka telah dikutip. Jadi, alih-alih a..b@example.net
Anda harus menulis "a..b"@example.net
, yang secara semantik setara.
Pembatasan lebih lanjut
SMTP (sebagaimana didefinisikan dalam RFC 5321 ) lebih lanjut membatasi rangkaian alamat email yang valid (atau sebenarnya: nama kotak surat). Tampaknya masuk akal untuk menerapkan tata bahasa yang lebih ketat ini, sehingga alamat email yang cocok sebenarnya dapat digunakan untuk mengirim email.
RFC 5321 pada dasarnya menyisakan bagian "lokal" (yaitu bagian sebelum tanda @), tetapi lebih ketat pada bagian domain (yaitu bagian setelah tanda @). Ini memungkinkan hanya nama host yang menggantikan dot-atom dan address literal sebagai pengganti literal domain.
Tata bahasa yang disajikan dalam RFC 5321 terlalu lunak jika menyangkut nama host dan alamat IP. Saya mengambil kebebasan untuk "memperbaiki" aturan yang dimaksud, menggunakan konsep ini dan RFC 1034 sebagai pedoman. Inilah regex yang dihasilkan.
([-!#-'*+/-9=?A-Z^-~]+(\.[-!#-'*+/-9=?A-Z^-~]+)*|"([]!#-[^-~ \t]|(\\[\t -~]))+")@([0-9A-Za-z]([0-9A-Za-z-]{0,61}[0-9A-Za-z])?(\.[0-9A-Za-z]([0-9A-Za-z-]{0,61}[0-9A-Za-z])?)*|\[((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}|IPv6:((((0|[1-9A-Fa-f][0-9A-Fa-f]{0,3}):){6}|::((0|[1-9A-Fa-f][0-9A-Fa-f]{0,3}):){5}|[0-9A-Fa-f]{0,4}::((0|[1-9A-Fa-f][0-9A-Fa-f]{0,3}):){4}|(((0|[1-9A-Fa-f][0-9A-Fa-f]{0,3}):)?(0|[1-9A-Fa-f][0-9A-Fa-f]{0,3}))?::((0|[1-9A-Fa-f][0-9A-Fa-f]{0,3}):){3}|(((0|[1-9A-Fa-f][0-9A-Fa-f]{0,3}):){0,2}(0|[1-9A-Fa-f][0-9A-Fa-f]{0,3}))?::((0|[1-9A-Fa-f][0-9A-Fa-f]{0,3}):){2}|(((0|[1-9A-Fa-f][0-9A-Fa-f]{0,3}):){0,3}(0|[1-9A-Fa-f][0-9A-Fa-f]{0,3}))?::(0|[1-9A-Fa-f][0-9A-Fa-f]{0,3}):|(((0|[1-9A-Fa-f][0-9A-Fa-f]{0,3}):){0,4}(0|[1-9A-Fa-f][0-9A-Fa-f]{0,3}))?::)((0|[1-9A-Fa-f][0-9A-Fa-f]{0,3}):(0|[1-9A-Fa-f][0-9A-Fa-f]{0,3})|(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3})|(((0|[1-9A-Fa-f][0-9A-Fa-f]{0,3}):){0,5}(0|[1-9A-Fa-f][0-9A-Fa-f]{0,3}))?::(0|[1-9A-Fa-f][0-9A-Fa-f]{0,3})|(((0|[1-9A-Fa-f][0-9A-Fa-f]{0,3}):){0,6}(0|[1-9A-Fa-f][0-9A-Fa-f]{0,3}))?::)|(?!IPv6:)[0-9A-Za-z-]*[0-9A-Za-z]:[!-Z^-~]+)])
Perhatikan bahwa tergantung pada kasus penggunaan, Anda mungkin tidak ingin mengizinkan "General-address-literal" di regex Anda. Juga perhatikan bahwa saya menggunakan lookahead negatif (?!IPv6:)
di regex akhir untuk mencegah bagian "General-address-literal" untuk mencocokkan alamat IPv6 yang salah. Beberapa prosesor regex tidak mendukung tampilan negatif. Hapus substring |(?!IPv6:)[0-9A-Za-z-]*[0-9A-Za-z]:[!-Z^-~]+
dari regex jika Anda ingin mengeluarkan seluruh bagian "General-address-literal".
Inilah derivasi:
Let-dig = ALPHA / DIGIT
=~ [0-9A-Za-z]
Ldh-str = *( ALPHA / DIGIT / "-" ) Let-dig
=~ [0-9A-Za-z-]*[0-9A-Za-z]
(regex is updated to make sure sub-domains are max. 63 charactes long - RFC 1034 section 3.5)
sub-domain = Let-dig [Ldh-str]
=~ [0-9A-Za-z]([0-9A-Za-z-]{0,61}[0-9A-Za-z])?
Domain = sub-domain *("." sub-domain)
=~ [0-9A-Za-z]([0-9A-Za-z-]{0,61}[0-9A-Za-z])?(\.[0-9A-Za-z]([0-9A-Za-z-]{0,61}[0-9A-Za-z])?)*
Snum = 1*3DIGIT
=~ [0-9]{1,3}
(suggested replacement for "Snum")
ip4-octet = DIGIT / %x31-39 DIGIT / "1" 2DIGIT / "2" %x30-34 DIGIT / "25" %x30-35
=~ 25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9]
IPv4-address-literal = Snum 3("." Snum)
=~ [0-9]{1,3}(\.[0-9]{1,3}){3}
(suggested replacement for "IPv4-address-literal")
ip4-address = ip4-octet 3("." ip4-octet)
=~ (25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}
(suggested replacement for "IPv6-hex")
ip6-h16 = "0" / ( (%x49-57 / %x65-70 /%x97-102) 0*3(%x48-57 / %x65-70 /%x97-102) )
=~ 0|[1-9A-Fa-f][0-9A-Fa-f]{0,3}
(not from RFC)
ls32 = ip6-h16 ":" ip6-h16 / ip4-address
=~ (0|[1-9A-Fa-f][0-9A-Fa-f]{0,3}):(0|[1-9A-Fa-f][0-9A-Fa-f]{0,3})|(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}
(suggested replacement of "IPv6-addr")
ip6-address = 6(ip6-h16 ":") ls32
/ "::" 5(ip6-h16 ":") ls32
/ [ ip6-h16 ] "::" 4(ip6-h16 ":") ls32
/ [ *1(ip6-h16 ":") ip6-h16 ] "::" 3(ip6-h16 ":") ls32
/ [ *2(ip6-h16 ":") ip6-h16 ] "::" 2(ip6-h16 ":") ls32
/ [ *3(ip6-h16 ":") ip6-h16 ] "::" ip6-h16 ":" ls32
/ [ *4(ip6-h16 ":") ip6-h16 ] "::" ls32
/ [ *5(ip6-h16 ":") ip6-h16 ] "::" ip6-h16
/ [ *6(ip6-h16 ":") ip6-h16 ] "::"
=~ (((0|[1-9A-Fa-f][0-9A-Fa-f]{0,3}):){6}|::((0|[1-9A-Fa-f][0-9A-Fa-f]{0,3}):){5}|[0-9A-Fa-f]{0,4}::((0|[1-9A-Fa-f][0-9A-Fa-f]{0,3}):){4}|(((0|[1-9A-Fa-f][0-9A-Fa-f]{0,3}):)?(0|[1-9A-Fa-f][0-9A-Fa-f]{0,3}))?::((0|[1-9A-Fa-f][0-9A-Fa-f]{0,3}):){3}|(((0|[1-9A-Fa-f][0-9A-Fa-f]{0,3}):){0,2}(0|[1-9A-Fa-f][0-9A-Fa-f]{0,3}))?::((0|[1-9A-Fa-f][0-9A-Fa-f]{0,3}):){2}|(((0|[1-9A-Fa-f][0-9A-Fa-f]{0,3}):){0,3}(0|[1-9A-Fa-f][0-9A-Fa-f]{0,3}))?::(0|[1-9A-Fa-f][0-9A-Fa-f]{0,3}):|(((0|[1-9A-Fa-f][0-9A-Fa-f]{0,3}):){0,4}(0|[1-9A-Fa-f][0-9A-Fa-f]{0,3}))?::)((0|[1-9A-Fa-f][0-9A-Fa-f]{0,3}):(0|[1-9A-Fa-f][0-9A-Fa-f]{0,3})|(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3})|(((0|[1-9A-Fa-f][0-9A-Fa-f]{0,3}):){0,5}(0|[1-9A-Fa-f][0-9A-Fa-f]{0,3}))?::(0|[1-9A-Fa-f][0-9A-Fa-f]{0,3})|(((0|[1-9A-Fa-f][0-9A-Fa-f]{0,3}):){0,6}(0|[1-9A-Fa-f][0-9A-Fa-f]{0,3}))?::
IPv6-address-literal = "IPv6:" ip6-address
=~ IPv6:((((0|[1-9A-Fa-f][0-9A-Fa-f]{0,3}):){6}|::((0|[1-9A-Fa-f][0-9A-Fa-f]{0,3}):){5}|[0-9A-Fa-f]{0,4}::((0|[1-9A-Fa-f][0-9A-Fa-f]{0,3}):){4}|(((0|[1-9A-Fa-f][0-9A-Fa-f]{0,3}):)?(0|[1-9A-Fa-f][0-9A-Fa-f]{0,3}))?::((0|[1-9A-Fa-f][0-9A-Fa-f]{0,3}):){3}|(((0|[1-9A-Fa-f][0-9A-Fa-f]{0,3}):){0,2}(0|[1-9A-Fa-f][0-9A-Fa-f]{0,3}))?::((0|[1-9A-Fa-f][0-9A-Fa-f]{0,3}):){2}|(((0|[1-9A-Fa-f][0-9A-Fa-f]{0,3}):){0,3}(0|[1-9A-Fa-f][0-9A-Fa-f]{0,3}))?::(0|[1-9A-Fa-f][0-9A-Fa-f]{0,3}):|(((0|[1-9A-Fa-f][0-9A-Fa-f]{0,3}):){0,4}(0|[1-9A-Fa-f][0-9A-Fa-f]{0,3}))?::)((0|[1-9A-Fa-f][0-9A-Fa-f]{0,3}):(0|[1-9A-Fa-f][0-9A-Fa-f]{0,3})|(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3})|(((0|[1-9A-Fa-f][0-9A-Fa-f]{0,3}):){0,5}(0|[1-9A-Fa-f][0-9A-Fa-f]{0,3}))?::(0|[1-9A-Fa-f][0-9A-Fa-f]{0,3})|(((0|[1-9A-Fa-f][0-9A-Fa-f]{0,3}):){0,6}(0|[1-9A-Fa-f][0-9A-Fa-f]{0,3}))?::)
Standardized-tag = Ldh-str
=~ [0-9A-Za-z-]*[0-9A-Za-z]
dcontent = %d33-90 / %d94-126
=~ [!-Z^-~]
General-address-literal = Standardized-tag ":" 1*dcontent
=~ [0-9A-Za-z-]*[0-9A-Za-z]:[!-Z^-~]+
address-literal = "[" ( IPv4-address-literal / IPv6-address-literal / General-address-literal ) "]"
=~ \[((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}|IPv6:((((0|[1-9A-Fa-f][0-9A-Fa-f]{0,3}):){6}|::((0|[1-9A-Fa-f][0-9A-Fa-f]{0,3}):){5}|[0-9A-Fa-f]{0,4}::((0|[1-9A-Fa-f][0-9A-Fa-f]{0,3}):){4}|(((0|[1-9A-Fa-f][0-9A-Fa-f]{0,3}):)?(0|[1-9A-Fa-f][0-9A-Fa-f]{0,3}))?::((0|[1-9A-Fa-f][0-9A-Fa-f]{0,3}):){3}|(((0|[1-9A-Fa-f][0-9A-Fa-f]{0,3}):){0,2}(0|[1-9A-Fa-f][0-9A-Fa-f]{0,3}))?::((0|[1-9A-Fa-f][0-9A-Fa-f]{0,3}):){2}|(((0|[1-9A-Fa-f][0-9A-Fa-f]{0,3}):){0,3}(0|[1-9A-Fa-f][0-9A-Fa-f]{0,3}))?::(0|[1-9A-Fa-f][0-9A-Fa-f]{0,3}):|(((0|[1-9A-Fa-f][0-9A-Fa-f]{0,3}):){0,4}(0|[1-9A-Fa-f][0-9A-Fa-f]{0,3}))?::)((0|[1-9A-Fa-f][0-9A-Fa-f]{0,3}):(0|[1-9A-Fa-f][0-9A-Fa-f]{0,3})|(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3})|(((0|[1-9A-Fa-f][0-9A-Fa-f]{0,3}):){0,5}(0|[1-9A-Fa-f][0-9A-Fa-f]{0,3}))?::(0|[1-9A-Fa-f][0-9A-Fa-f]{0,3})|(((0|[1-9A-Fa-f][0-9A-Fa-f]{0,3}):){0,6}(0|[1-9A-Fa-f][0-9A-Fa-f]{0,3}))?::)|(?!IPv6:)[0-9A-Za-z-]*[0-9A-Za-z]:[!-Z^-~]+)]
Mailbox = Local-part "@" ( Domain / address-literal )
=~ ([-!#-'*+/-9=?A-Z^-~]+(\.[-!#-'*+/-9=?A-Z^-~]+)*|"([]!#-[^-~ \t]|(\\[\t -~]))+")@([0-9A-Za-z]([0-9A-Za-z-]{0,61}[0-9A-Za-z])?(\.[0-9A-Za-z]([0-9A-Za-z-]{0,61}[0-9A-Za-z])?)*|\[((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}|IPv6:((((0|[1-9A-Fa-f][0-9A-Fa-f]{0,3}):){6}|::((0|[1-9A-Fa-f][0-9A-Fa-f]{0,3}):){5}|[0-9A-Fa-f]{0,4}::((0|[1-9A-Fa-f][0-9A-Fa-f]{0,3}):){4}|(((0|[1-9A-Fa-f][0-9A-Fa-f]{0,3}):)?(0|[1-9A-Fa-f][0-9A-Fa-f]{0,3}))?::((0|[1-9A-Fa-f][0-9A-Fa-f]{0,3}):){3}|(((0|[1-9A-Fa-f][0-9A-Fa-f]{0,3}):){0,2}(0|[1-9A-Fa-f][0-9A-Fa-f]{0,3}))?::((0|[1-9A-Fa-f][0-9A-Fa-f]{0,3}):){2}|(((0|[1-9A-Fa-f][0-9A-Fa-f]{0,3}):){0,3}(0|[1-9A-Fa-f][0-9A-Fa-f]{0,3}))?::(0|[1-9A-Fa-f][0-9A-Fa-f]{0,3}):|(((0|[1-9A-Fa-f][0-9A-Fa-f]{0,3}):){0,4}(0|[1-9A-Fa-f][0-9A-Fa-f]{0,3}))?::)((0|[1-9A-Fa-f][0-9A-Fa-f]{0,3}):(0|[1-9A-Fa-f][0-9A-Fa-f]{0,3})|(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3})|(((0|[1-9A-Fa-f][0-9A-Fa-f]{0,3}):){0,5}(0|[1-9A-Fa-f][0-9A-Fa-f]{0,3}))?::(0|[1-9A-Fa-f][0-9A-Fa-f]{0,3})|(((0|[1-9A-Fa-f][0-9A-Fa-f]{0,3}):){0,6}(0|[1-9A-Fa-f][0-9A-Fa-f]{0,3}))?::)|(?!IPv6:)[0-9A-Za-z-]*[0-9A-Za-z]:[!-Z^-~]+)])
Validasi input pengguna
Kasing penggunaan umum adalah validasi input pengguna, misalnya pada formulir html. Dalam hal ini, biasanya masuk akal untuk mencegah literal alamat dan membutuhkan setidaknya dua label dalam nama host. Mengambil regex RFC 5321 yang ditingkatkan dari bagian sebelumnya sebagai dasar, ekspresi yang dihasilkan adalah:
([-!#-'*+/-9=?A-Z^-~]+(\.[-!#-'*+/-9=?A-Z^-~]+)*|"([]!#-[^-~ \t]|(\\[\t -~]))+")@[0-9A-Za-z]([0-9A-Za-z-]{0,61}[0-9A-Za-z])?(\.[0-9A-Za-z]([0-9A-Za-z-]{0,61}[0-9A-Za-z])?)+
Saya tidak merekomendasikan untuk membatasi bagian lokal lebih lanjut, misalnya dengan menghalangi string yang dikutip, karena kita tidak tahu jenis nama kotak surat apa yang dibolehkan oleh beberapa host (suka "a..b"@example.net
atau bahkan "a b"@example.net
).
Saya juga tidak merekomendasikan memvalidasi secara eksplisit terhadap daftar domain tingkat atas literal atau bahkan memaksakan batasan panjang (ingat bagaimana ".museum" tidak valid [a-z]{2,4}
), tetapi jika Anda harus:
([-!#-'*+/-9=?A-Z^-~]+(\.[-!#-'*+/-9=?A-Z^-~]+)*|"([]!#-[^-~ \t]|(\\[\t -~]))+")@([0-9A-Za-z]([0-9A-Za-z-]{0,61}[0-9A-Za-z])?\.)*(net|org|com|info|
dll ...)
Pastikan untuk tetap memperbarui regex Anda jika Anda memutuskan untuk turun ke jalur validasi domain tingkat atas eksplisit.
Pertimbangan lebih lanjut
Ketika hanya menerima nama host di bagian domain (setelah tanda @), regex di atas hanya menerima label dengan paling banyak 63 karakter, sebagaimana seharusnya. Namun, mereka tidak memaksakan fakta bahwa seluruh nama host harus paling panjang 253 karakter (termasuk titik-titik). Meskipun batasan ini secara tegas masih teratur, itu tidak layak untuk membuat regex yang menggabungkan aturan ini.
Pertimbangan lain, terutama ketika menggunakan regex untuk validasi input, adalah umpan balik kepada pengguna. Jika pengguna memasukkan alamat yang salah, alangkah baiknya memberikan sedikit lebih banyak umpan balik daripada "alamat yang salah secara sintaksis" yang sederhana. Dengan regex "vanilla" ini tidak mungkin.
Kedua pertimbangan ini dapat diatasi dengan menguraikan alamat. Batasan panjang ekstra pada nama host dalam beberapa kasus juga dapat diatasi dengan menggunakan regex tambahan yang memeriksanya, dan mencocokkan alamat dengan kedua ekspresi.
Tidak ada regex dalam jawaban ini yang dioptimalkan untuk kinerja. Jika kinerja merupakan masalah, Anda harus melihat apakah (dan bagaimana) regex pilihan Anda dapat dioptimalkan.