Câu trả lời nhanh
Sử dụng biểu thức chính sau để xác thực đầu vào:
([-!#-'*+/-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])?)+
Địa chỉ phù hợp với regex này:
- có một phần cục bộ (tức là phần trước @ -sign) tuân thủ nghiêm ngặt với RFC 5321/5322,
- có một phần tên miền (tức là phần sau @ -sign) là tên máy chủ có ít nhất hai nhãn, mỗi nhãn dài tối đa 63 ký tự.
Hạn chế thứ hai là một hạn chế đối với RFC 5321/5322.
Xây dựng câu trả lời
Sử dụng biểu thức chính quy nhận dạng địa chỉ email có thể hữu ích trong các tình huống khác nhau: ví dụ: quét địa chỉ email trong tài liệu, để xác thực đầu vào của người dùng hoặc như một ràng buộc toàn vẹn trên kho lưu trữ dữ liệu.
Tuy nhiên, cần lưu ý rằng nếu bạn muốn tìm hiểu xem địa chỉ có thực sự đề cập đến hộp thư hiện có hay không, không có sự thay thế nào cho việc gửi tin nhắn đến địa chỉ. Nếu bạn chỉ muốn kiểm tra xem một địa chỉ có đúng ngữ pháp hay không thì bạn có thể sử dụng một biểu thức chính quy, nhưng lưu ý rằng""@[]
là một địa chỉ email đúng ngữ pháp mà chắc chắn không đề cập đến một hộp thư hiện có.
Cú pháp của địa chỉ email đã được xác định trong các RFC khác nhau , đáng chú ý nhất là RFC 822 và RFC 5322 . RFC 822 nên được coi là tiêu chuẩn "nguyên bản" và RFC 5322 là tiêu chuẩn mới nhất. Cú pháp được định nghĩa trong RFC 822 là các tiêu chuẩn nhẹ nhàng nhất và tiếp theo đã hạn chế cú pháp ngày càng xa hơn, trong đó các hệ thống hoặc dịch vụ mới hơn sẽ nhận ra cú pháp lỗi thời, nhưng không bao giờ tạo ra nó.
Trong câu trả lời này, tôi sẽ lấy địa chỉ email của Cameron có nghĩa addr-spec
là được định nghĩa trong RFC (nghĩa là jdoe@example.org
, nhưng không "John Doe"<jdoe@example.org>
, cũng không some-group:jdoe@example.org,mrx@exampel.org;
).
Có một vấn đề với việc dịch các cú pháp RFC thành regexes: các cú pháp không thường xuyên! Điều này là do chúng cho phép nhận xét tùy chọn trong các địa chỉ email có thể được lồng vô hạn, trong khi lồng vô hạn không thể được mô tả bằng một biểu thức thông thường. Để quét hoặc xác thực các địa chỉ có chứa các bình luận, bạn cần một trình phân tích cú pháp hoặc các biểu thức mạnh hơn. (Lưu ý rằng các ngôn ngữ như Perl có cấu trúc để mô tả ngữ pháp miễn phí theo ngữ cảnh theo cách giống như biểu thức chính quy.) Trong câu trả lời này, tôi sẽ bỏ qua các bình luận và chỉ xem xét các biểu thức chính quy thông thường.
RFC xác định cú pháp cho thông điệp email, không phải cho địa chỉ email như vậy. Địa chỉ có thể xuất hiện trong các trường tiêu đề khác nhau và đây là nơi chúng được xác định chủ yếu. Khi chúng xuất hiện trong các trường tiêu đề, địa chỉ có thể chứa (giữa các thẻ từ vựng) khoảng trắng, nhận xét và thậm chí ngắt dòng. Về mặt ngữ nghĩa, điều này không có ý nghĩa gì. Bằng cách xóa khoảng trắng này, v.v. khỏi một địa chỉ, bạn sẽ có được một biểu diễn chính tắc tương đương về mặt ngữ nghĩa . Vì vậy, các đại diện kinh điển của first. last (comment) @ [3.5.7.9]
là first.last@[3.5.7.9]
.
Các cú pháp khác nhau nên được sử dụng cho các mục đích khác nhau. Nếu bạn muốn quét địa chỉ email trong tài liệu (có thể rất cũ), có thể nên sử dụng cú pháp như được định nghĩa trong RFC 822. Mặt khác, nếu bạn muốn xác thực đầu vào của người dùng, bạn có thể muốn sử dụng cú pháp như được định nghĩa trong RFC 5322, có lẽ chỉ chấp nhận các biểu diễn chính tắc. Bạn nên quyết định cú pháp nào áp dụng cho trường hợp cụ thể của bạn.
Tôi sử dụng các biểu thức chính quy "mở rộng" POSIX trong câu trả lời này, giả sử một bộ ký tự tương thích ASCII.
RFC 822
Tôi đến biểu hiện thường xuyên sau đây. Tôi mời mọi người dùng thử và phá vỡ nó. Nếu bạn tìm thấy bất kỳ dương tính giả hoặc phủ định sai, xin vui lòng gửi chúng trong một bình luận và tôi sẽ cố gắng sửa biểu thức càng sớm càng tốt.
([^][()<>@,;:\\". \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)*]))*
Tôi tin rằng nó hoàn toàn phù hợp với RFC 822 bao gồm cả errata . Nó chỉ nhận ra địa chỉ email ở dạng chính tắc của họ. Đối với một regex nhận ra (gấp) khoảng trắng, xem đạo hàm bên dưới.
Đạo hàm cho thấy cách tôi đến biểu thức. Tôi liệt kê tất cả các quy tắc ngữ pháp có liên quan từ RFC chính xác khi chúng xuất hiện, theo sau là regex tương ứng. Khi một lỗi sai đã được xuất bản, tôi đưa ra một biểu thức riêng cho quy tắc ngữ pháp đã sửa (được đánh dấu là "erratum") và sử dụng phiên bản cập nhật như một biểu hiện phụ trong các biểu thức chính quy tiếp theo.
Như đã nêu trong đoạn 3.1.4. của RFC 822 khoảng trắng tuyến tính tùy chọn có thể được chèn giữa các mã thông báo từ vựng. Khi áp dụng, tôi đã mở rộng các biểu thức để phù hợp với quy tắc này và đánh dấu kết quả bằng "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
Tôi đến biểu hiện thường xuyên sau đây. Tôi mời mọi người dùng thử và phá vỡ nó. Nếu bạn tìm thấy bất kỳ dương tính giả hoặc phủ định sai, xin vui lòng gửi chúng trong một bình luận và tôi sẽ cố gắng sửa biểu thức càng sớm càng tốt.
([-!#-'*+/-9=?A-Z^-~]+(\.[-!#-'*+/-9=?A-Z^-~]+)*|"([]!#-[^-~ \t]|(\\[\t -~]))+")@([-!#-'*+/-9=?A-Z^-~]+(\.[-!#-'*+/-9=?A-Z^-~]+)*|\[[\t -Z^-~]*])
Tôi tin rằng nó hoàn toàn phù hợp với RFC 5322 bao gồm cả errata . Nó chỉ nhận ra địa chỉ email ở dạng chính tắc của họ. Đối với một regex nhận ra (gấp) khoảng trắng, xem đạo hàm bên dưới.
Đạo hàm cho thấy cách tôi đến biểu thức. Tôi liệt kê tất cả các quy tắc ngữ pháp có liên quan từ RFC chính xác khi chúng xuất hiện, theo sau là regex tương ứng. Đối với các quy tắc bao gồm khoảng trắng (gấp) không liên quan về mặt ngữ nghĩa, tôi đưa ra một biểu thức chính được đánh dấu "(chuẩn hóa)" không chấp nhận khoảng trắng này.
Tôi đã bỏ qua tất cả các quy tắc "quan sát" từ RFC. Điều này có nghĩa là các biểu thức chỉ phù hợp với các địa chỉ email tuân thủ nghiêm ngặt RFC 5322. Nếu bạn phải khớp các địa chỉ "cũ" (như ngữ pháp lỏng hơn bao gồm các quy tắc "obs-"), bạn có thể sử dụng một trong các biểu thức RFC 822 từ đoạn trước.
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^-~]*])
Lưu ý rằng một số nguồn (đáng chú ý là w3c ) cho rằng RFC 5322 quá nghiêm ngặt đối với phần cục bộ (tức là phần trước @ -sign). Điều này là do "..", "a..b" và "a." không phải là các nguyên tử chấm hợp lệ, trong khi chúng có thể được sử dụng làm tên hộp thư. RFC, tuy nhiên, không cho phép cho các bộ phận địa phương như thế này, ngoại trừ việc họ đã được trích dẫn. Vì vậy, thay vì a..b@example.net
bạn nên viết "a..b"@example.net
, đó là tương đương về mặt ngữ nghĩa.
Hạn chế hơn nữa
SMTP (như được định nghĩa trong RFC 5321 ) tiếp tục hạn chế tập hợp các địa chỉ email hợp lệ (hoặc thực tế: tên hộp thư). Có vẻ hợp lý để áp đặt ngữ pháp chặt chẽ hơn này, để địa chỉ email phù hợp thực sự có thể được sử dụng để gửi email.
Về cơ bản, RFC 5321 chỉ để lại phần "cục bộ" (tức là phần trước phần @ -ign), nhưng chặt chẽ hơn về phần miền (tức là phần sau phần @ -ign). Nó chỉ cho phép lưu trữ tên thay cho các nguyên tử dấu chấm và địa chỉ bằng chữ thay cho tên miền.
Ngữ pháp được trình bày trong RFC 5321 quá nhẹ nhàng khi nói đến cả tên máy chủ và địa chỉ IP. Tôi đã tự do "sửa chữa" các quy tắc trong câu hỏi, sử dụng dự thảo này và RFC 1034 làm hướng dẫn. Đây là regex kết quả.
([-!#-'*+/-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^-~]+)])
Lưu ý rằng tùy thuộc vào trường hợp sử dụng, bạn có thể không muốn cho phép "Địa chỉ chung theo nghĩa đen" trong biểu thức chính thức của mình. Cũng lưu ý rằng tôi đã sử dụng giao diện phủ định (?!IPv6:)
trong regex cuối cùng để ngăn phần "Địa chỉ chung theo nghĩa đen" khớp với các địa chỉ IPv6 không đúng định dạng. Một số bộ xử lý regex không hỗ trợ giao diện tiêu cực. Xóa chuỗi con |(?!IPv6:)[0-9A-Za-z-]*[0-9A-Za-z]:[!-Z^-~]+
khỏi regex nếu bạn muốn lấy toàn bộ phần "Địa chỉ chung" theo nghĩa đen.
Đây là đạo hàm:
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^-~]+)])
Xác thực người dùng
Trường hợp sử dụng phổ biến là xác thực nhập liệu của người dùng, ví dụ trên biểu mẫu html. Trong trường hợp đó, thường là hợp lý để loại trừ địa chỉ bằng chữ và yêu cầu ít nhất hai nhãn trong tên máy chủ. Lấy regex RFC 5321 được cải tiến từ phần trước làm cơ sở, biểu thức kết quả sẽ là:
([-!#-'*+/-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])?)+
Tôi không khuyên bạn nên hạn chế phần cục bộ hơn nữa, ví dụ: bằng cách loại trừ các chuỗi được trích dẫn, vì chúng tôi không biết loại tên hộp thư nào mà một số máy chủ cho phép (thích "a..b"@example.net
hoặc thậm chí "a b"@example.net
).
Tôi cũng không khuyên bạn nên xác nhận rõ ràng đối với danh sách các tên miền cấp cao nhất hoặc thậm chí áp đặt các ràng buộc về độ dài (hãy nhớ cách ".museum" bị vô hiệu [a-z]{2,4}
), nhưng nếu bạn phải:
([-!#-'*+/-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|
Vân vân...)
Đảm bảo cập nhật biểu thức chính quy của bạn nếu bạn quyết định đi theo con đường xác thực tên miền cấp cao rõ ràng.
Cân nhắc thêm
Khi chỉ chấp nhận tên máy chủ trong phần tên miền (sau @ -sign), các biểu thức trên chỉ chấp nhận các nhãn có tối đa 63 ký tự, nếu cần. Tuy nhiên, họ không thực thi thực tế là toàn bộ tên máy chủ phải dài tối đa 253 ký tự (bao gồm cả dấu chấm). Mặc dù hạn chế này được nói một cách nghiêm túc vẫn đều đặn, nhưng việc đưa ra một biểu thức kết hợp quy tắc này là không khả thi.
Một cân nhắc khác, đặc biệt là khi sử dụng các biểu thức chính để xác thực đầu vào, là phản hồi cho người dùng. Nếu người dùng nhập địa chỉ không chính xác, sẽ tốt hơn nếu cung cấp thêm một chút thông tin phản hồi so với "địa chỉ cú pháp sai" đơn giản. Với regexes "vanilla", điều này là không thể.
Hai cân nhắc này có thể được giải quyết bằng cách phân tích địa chỉ. Trong một số trường hợp, ràng buộc độ dài thêm đối với tên máy chủ có thể được giải quyết bằng cách sử dụng biểu thức chính bổ sung để kiểm tra nó và khớp địa chỉ với cả hai biểu thức.
Không có biểu thức nào trong câu trả lời này được tối ưu hóa cho hiệu suất. Nếu hiệu suất là một vấn đề, bạn nên xem liệu (và làm thế nào) biểu thức bạn chọn có thể được tối ưu hóa hay không.