คำตอบที่รวดเร็ว
ใช้ regex ต่อไปนี้สำหรับการตรวจสอบความถูกต้องของอินพุต:
([-!#-'*+/-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])?)+
ที่อยู่ที่ตรงกับ regex นี้:
- มีส่วนท้องที่ (เช่นส่วนที่อยู่ก่อนเครื่องหมาย @) ซึ่งเป็นไปตาม RFC 5321/5322 อย่างเคร่งครัด
- มีส่วนโดเมน (เช่นส่วนหลัง @- เซ็นชื่อ) ที่เป็นชื่อโฮสต์ที่มีป้ายกำกับอย่างน้อยสองป้ายแต่ละรายการมีความยาวสูงสุด 63 อักขระ
ข้อ จำกัด ที่สองคือข้อ จำกัด ของ RFC 5321/5322
คำตอบที่ซับซ้อน
การใช้นิพจน์ทั่วไปที่จดจำที่อยู่อีเมลอาจมีประโยชน์ในสถานการณ์ต่าง ๆ : ตัวอย่างเช่นการสแกนที่อยู่อีเมลในเอกสารเพื่อตรวจสอบการป้อนข้อมูลของผู้ใช้หรือเป็นข้อ จำกัด ด้านความสมบูรณ์ของที่เก็บข้อมูล
อย่างไรก็ตามควรสังเกตว่าถ้าคุณต้องการตรวจสอบว่าที่อยู่อ้างอิงถึงกล่องจดหมายที่มีอยู่จริงหรือไม่ไม่มีการส่งข้อความไปยังที่อยู่นั้นแทน หากคุณต้องการตรวจสอบว่าที่อยู่นั้นถูกต้องทางไวยากรณ์หรือไม่คุณสามารถใช้นิพจน์ปกติได้ แต่โปรดทราบว่า""@[]
เป็นที่อยู่อีเมลที่ถูกต้องตามหลักไวยากรณ์ที่แน่นอนไม่ได้อ้างถึงกล่องจดหมายที่มีอยู่
ไวยากรณ์ของที่อยู่อีเมลที่ได้รับการกำหนดในหลายRFCsสะดุดตาที่สุดRFC 822และRFC 5322 ควรมองว่า RFC 822 เป็นมาตรฐาน "ดั้งเดิม" และ RFC 5322 เป็นมาตรฐานล่าสุด ไวยากรณ์ที่กำหนดใน RFC 822 เป็นมาตรฐานที่ผ่อนปรนที่สุดและตามมาได้ จำกัด ไวยากรณ์เพิ่มเติมและเพิ่มเติมซึ่งระบบใหม่หรือบริการใหม่ควรรับรู้ไวยากรณ์ที่ล้าสมัย แต่ไม่เคยผลิตมัน
ในคำตอบนี้ผมจะพา“ที่อยู่อีเมล” หมายถึงการaddr-spec
ที่กำหนดไว้ใน RFCs (เช่นjdoe@example.org
แต่ไม่ได้"John Doe"<jdoe@example.org>
หรือsome-group:jdoe@example.org,mrx@exampel.org;
)
มีปัญหาหนึ่งในการแปลไวยากรณ์ RFC เป็น regexes: ไวยากรณ์ไม่ปกติ! นี่เป็นเพราะพวกเขาอนุญาตให้มีความคิดเห็นเพิ่มเติมในที่อยู่อีเมลที่สามารถซ้อนกันได้อย่างไม่มีที่สิ้นสุดในขณะที่การทำรังไม่สิ้นสุดไม่สามารถอธิบายได้ด้วยนิพจน์ทั่วไป ในการสแกนหรือตรวจสอบที่อยู่ที่มีความคิดเห็นคุณจะต้องมีตัวแยกวิเคราะห์หรือนิพจน์ที่มีประสิทธิภาพมากขึ้น (โปรดทราบว่าภาษาอย่าง Perl มีโครงสร้างเพื่ออธิบายไวยากรณ์ฟรีตามบริบทในลักษณะคล้าย regex) ในคำตอบนี้ฉันจะไม่สนใจความคิดเห็นและพิจารณาเฉพาะการแสดงออกปกติที่เหมาะสมเท่านั้น
RFCs กำหนดไวยากรณ์สำหรับข้อความอีเมลไม่ใช่สำหรับที่อยู่อีเมลเช่นนั้น ที่อยู่อาจปรากฏในฟิลด์ส่วนหัวต่างๆและนี่คือที่ที่พวกเขาถูกกำหนดเป็นหลัก เมื่อปรากฏในที่อยู่ฟิลด์ส่วนหัวที่อยู่อาจมีช่องว่าง (ระหว่างโทเค็นคำศัพท์) ช่องว่างความคิดเห็นและแม้แต่การแบ่งบรรทัด ความหมายนี้ไม่มีความสำคัญอย่างไรก็ตาม โดยการลบช่องว่างนี้และอื่น ๆ จากที่อยู่คุณจะได้รับความหมายเทียบเท่าตัวแทนที่ยอมรับ ดังนั้นการเป็นตัวแทนยอมรับคือfirst. last (comment) @ [3.5.7.9]
first.last@[3.5.7.9]
ควรใช้ไวยากรณ์ที่แตกต่างกันเพื่อจุดประสงค์ที่แตกต่างกัน หากคุณต้องการสแกนที่อยู่อีเมลในเอกสาร (อาจเก่ามาก) อาจเป็นความคิดที่ดีที่จะใช้ไวยากรณ์ตามที่กำหนดไว้ใน RFC 822 ในทางกลับกันหากคุณต้องการตรวจสอบความถูกต้องของข้อมูลผู้ใช้คุณอาจต้องการใช้ ไวยากรณ์ตามที่กำหนดไว้ใน RFC 5322 อาจยอมรับการยอมรับแบบบัญญัติเท่านั้น คุณควรตัดสินใจว่าจะใช้ไวยากรณ์ใดกับกรณีของคุณ
ฉันใช้การแสดงออกปกติ POSIX "ขยาย" ในคำตอบนี้สมมติว่าชุดอักขระที่รองรับ ASCII
RFC 822
ฉันมาถึงที่การแสดงออกปกติต่อไปนี้ ฉันขอเชิญชวนให้ทุกคนลองและทำลายมัน หากคุณพบว่ามีผลบวกปลอมหรือเป็นเท็จใด ๆ โปรดโพสต์ไว้ในความคิดเห็นและฉันจะพยายามแก้ไขนิพจน์โดยเร็วที่สุด
([^][()<>@,;:\\". \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)*]))*
ผมเชื่อว่ามัน complient อย่างเต็มที่กับ RFC 822 รวมทั้งคหบดี มันจะจดจำที่อยู่อีเมลในรูปแบบที่ยอมรับได้เท่านั้น สำหรับ regex ที่รู้จักช่องว่าง (การพับ) ให้ดูที่มาด้านล่าง
ที่มาแสดงให้เห็นว่าฉันมาถึงที่แสดงออก ฉันแสดงกฎไวยากรณ์ที่เกี่ยวข้องทั้งหมดจาก RFC ให้ตรงตามที่ปรากฏตามด้วย regex ที่เกี่ยวข้อง ที่เผยแพร่ erratum ฉันให้นิพจน์แยกต่างหากสำหรับกฎไวยากรณ์ที่ถูกต้อง (ทำเครื่องหมาย "erratum") และใช้เวอร์ชันที่อัปเดตเป็นนิพจน์ย่อยในนิพจน์ทั่วไปที่ตามมา
ตามที่ระบุในวรรค 3.1.4 ของ RFC 822 ตัวเลือก white white space อาจถูกแทรกระหว่างโทเค็นของคำศัพท์ ฉันได้ขยายการแสดงออกเพื่อรองรับกฎนี้และทำเครื่องหมายผลลัพธ์ด้วย "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
ฉันมาถึงที่การแสดงออกปกติต่อไปนี้ ฉันขอเชิญชวนให้ทุกคนลองและทำลายมัน หากคุณพบว่ามีผลบวกปลอมหรือเป็นเท็จใด ๆ โปรดโพสต์ไว้ในความคิดเห็นและฉันจะพยายามแก้ไขนิพจน์โดยเร็วที่สุด
([-!#-'*+/-9=?A-Z^-~]+(\.[-!#-'*+/-9=?A-Z^-~]+)*|"([]!#-[^-~ \t]|(\\[\t -~]))+")@([-!#-'*+/-9=?A-Z^-~]+(\.[-!#-'*+/-9=?A-Z^-~]+)*|\[[\t -Z^-~]*])
ผมเชื่อว่ามัน complient อย่างเต็มที่กับ RFC 5322 รวมทั้งคหบดี มันจะจดจำที่อยู่อีเมลในรูปแบบที่ยอมรับได้เท่านั้น สำหรับ regex ที่รู้จักช่องว่าง (การพับ) ให้ดูที่มาด้านล่าง
ที่มาแสดงให้เห็นว่าฉันมาถึงที่แสดงออก ฉันแสดงกฎไวยากรณ์ที่เกี่ยวข้องทั้งหมดจาก RFC ให้ตรงตามที่ปรากฏตามด้วย regex ที่เกี่ยวข้อง สำหรับกฎที่รวมถึงช่องว่างที่ไม่เกี่ยวข้องทางความหมาย (การพับ) ฉันต้องให้ regex แยกที่ระบุว่า "(ปกติ)" ที่ไม่ยอมรับช่องว่างนี้
ฉันเพิกเฉยต่อกฎ "obs-" ทั้งหมดจาก RFC ซึ่งหมายความว่า regexes จับคู่ที่อยู่อีเมลที่สอดคล้องกับ RFC 5322 อย่างเคร่งครัดเท่านั้น หากคุณต้องจับคู่ที่อยู่ "เก่า" (เช่นเดียวกับไวยากรณ์ที่หลวมรวมถึงกฎ "obs-") คุณสามารถใช้หนึ่งใน RFC 822 regexes จากย่อหน้าก่อนหน้า
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^-~]*])
โปรดทราบว่าบางแหล่งที่มา (สะดุดตาw3c ) อ้างว่า RFC 5322 นั้นเข้มงวดเกินไปในส่วนของโลคัล (เช่นส่วนที่อยู่หน้า @ -sign) นี่เป็นเพราะ ".. ", "a..b" และ "a." มีความไม่ถูกต้องจุดอะตอมขณะที่พวกเขาอาจจะใช้เป็นชื่อกล่องจดหมาย RFC แต่ไม่อนุญาตให้มีส่วนในท้องถิ่นเช่นนี้ยกเว้นว่าพวกเขาจะต้องมีการอ้าง ดังนั้นa..b@example.net
คุณควรเขียน"a..b"@example.net
ซึ่งเทียบเท่ากับความหมาย
ข้อ จำกัด เพิ่มเติม
SMTP (ตามที่กำหนดในRFC 5321 ) จำกัด ชุดของที่อยู่อีเมลที่ถูกต้องเพิ่มเติม (หรือจริง ๆ : ชื่อกล่องจดหมาย) ดูเหมือนว่ามีเหตุผลที่จะกำหนดไวยากรณ์ที่เข้มงวดนี้เพื่อให้สามารถใช้ที่อยู่อีเมลที่ตรงกันเพื่อส่งอีเมลได้
โดยทั่วไปแล้ว RFC 5321 จะแยกส่วน "local" เพียงอย่างเดียว (เช่นส่วนก่อน @ -sign) แต่จะเข้มงวดกว่าในส่วนของโดเมน (เช่นส่วนที่อยู่หลัง @ -sign) จะอนุญาตเฉพาะชื่อโฮสต์แทนที่ dot-atoms และตัวอักษรที่อยู่ในสถานที่ของตัวอักษรโดเมน
ไวยากรณ์ที่แสดงใน RFC 5321 นั้นอ่อนเกินไปเมื่อพูดถึงชื่อโฮสต์และที่อยู่ IP ฉันใช้เสรีภาพในการ "แก้ไข" กฎที่เป็นปัญหาโดยใช้ร่างนี้และRFC 1034เป็นแนวทาง นี่คือผลลัพธ์ของ regex
([-!#-'*+/-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^-~]+)])
โปรดทราบว่าขึ้นอยู่กับกรณีการใช้งานคุณอาจไม่ต้องการอนุญาตให้ "General-address-literal" ใน regex ของคุณ โปรดทราบว่าฉันใช้ lookahead เชิงลบ(?!IPv6:)
ใน regex สุดท้ายเพื่อป้องกันส่วน "General-address-literal" เพื่อจับคู่ที่อยู่ IPv6 ที่ผิดรูปแบบ ตัวประมวลผล regex บางตัวไม่รองรับ lookahead เชิงลบ ลบซับสตริง|(?!IPv6:)[0-9A-Za-z-]*[0-9A-Za-z]:[!-Z^-~]+
ออกจาก regex หากคุณต้องการนำส่วน "General-address-literal" ทั้งหมดออก
นี่คือที่มา:
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^-~]+)])
การตรวจสอบอินพุตของผู้ใช้
กรณีการใช้งานทั่วไปคือการตรวจสอบการป้อนข้อมูลของผู้ใช้ตัวอย่างเช่นในรูปแบบ html ในกรณีดังกล่าวมักจะเหมาะสมที่จะตัดทอนตัวอักษรที่อยู่และต้องมีป้ายกำกับอย่างน้อยสองรายการในชื่อโฮสต์ รับ RFC 5321 regex ที่ปรับปรุงแล้วจากส่วนก่อนหน้าเป็นพื้นฐานนิพจน์ผลลัพธ์จะเป็น:
([-!#-'*+/-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..b"@example.net
หรือแม้กระทั่ง"a b"@example.net
)
ฉันยังไม่แนะนำให้ตรวจสอบความถูกต้องอย่างชัดเจนกับรายการโดเมนระดับบนสุดตามตัวอักษรหรือแม้แต่การจำกัดความยาว (จำได้ว่า ".museum" ไม่ถูกต้อง[a-z]{2,4}
) แต่ถ้าคุณต้อง:
([-!#-'*+/-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|
ฯลฯ ...)
ตรวจสอบให้แน่ใจว่าได้อัปเดต regex ของคุณแล้วหากคุณตัดสินใจที่จะตรวจสอบความถูกต้องของโดเมนระดับบนสุด
ข้อควรพิจารณาเพิ่มเติม
เมื่อยอมรับเฉพาะชื่อโฮสต์ในส่วนโดเมน (หลังเครื่องหมาย @ -) regexes ด้านบนจะยอมรับเฉพาะป้ายกำกับที่มีความยาวสูงสุด 63 ตัวอักษรตามที่ควร อย่างไรก็ตามพวกเขาไม่บังคับใช้ความจริงที่ว่าชื่อโฮสต์ทั้งหมดจะต้องมีความยาวไม่เกิน 253 อักขระ (รวมถึงจุด) แม้ว่าข้อ จำกัด นี้จะพูดอย่างเคร่งครัดเป็นประจำ แต่ก็ไม่สามารถสร้าง regex ที่รวมกฎนี้ได้
ข้อควรพิจารณาอีกประการหนึ่งโดยเฉพาะอย่างยิ่งเมื่อใช้ regexes สำหรับการตรวจสอบความถูกต้องของข้อมูลคือข้อเสนอแนะต่อผู้ใช้ หากผู้ใช้ป้อนที่อยู่ที่ไม่ถูกต้องมันจะดีกว่าถ้าให้คำติชมเล็กน้อยกว่า "ที่อยู่ที่ไม่ถูกต้องทางไวยากรณ์" แบบง่าย ด้วย "วานิลลา" regexes นี้เป็นไปไม่ได้
ข้อควรพิจารณาสองข้อนี้สามารถแก้ไขได้ด้วยการแยกที่อยู่ ข้อจำกัดความยาวพิเศษของชื่อโฮสต์อาจในบางกรณีสามารถแก้ไขได้โดยใช้ regex พิเศษที่ตรวจสอบและจับคู่ที่อยู่กับทั้งสองนิพจน์
ไม่มี regexes ในคำตอบนี้เหมาะสำหรับประสิทธิภาพ หากประสิทธิภาพเป็นปัญหาคุณควรดูว่า (และวิธี) regex ที่คุณเลือกสามารถเพิ่มประสิทธิภาพได้หรือไม่