3. Syntax (语法)
3.1. Introduction (引言)
本节给出的语法定义了互联网消息的合法语法。符合本规范的消息必须 (MUST) 符合本节中的语法。如果本节中存在某个选项应该 (SHOULD) 被生成的情况,则在正文或语法旁边的注释中指出。
对于已定义的表达式,先给出语法和用法的简短描述,然后是ABNF语法,再然后是语义分析。以下使用但未另外指定的原始标记取自 [RFC5234] 附录B.1的"核心规则": CR, LF, CRLF, HTAB, SP, WSP, DQUOTE, DIGIT, ALPHA和VCHAR。
在某些定义中,将有名称以"obs-"开头的非终结符。这些"obs-"元素指的是第4节废弃语法中定义的标记。在所有情况下,出于生成合法互联网消息的目的,这些产生式都应被忽略,并且禁止 (MUST NOT) 用作此类消息的一部分。但是,在解释消息时,必须 (MUST) 将这些标记作为合法语法的一部分来遵守。从这个意义上讲,第3节定义了用于生成消息的语法,其中"obs-"元素将被忽略,而第4节添加了用于解释消息的语法。
核心概念:
| 概念 | 说明 |
|---|---|
| 生成消息 | 第3节语法,忽略obs-元素 |
| 解析消息 | 第3节+第4节,包含obs-元素 |
| obs-元素 | 废弃语法,禁止生成但必须解析 |
3.2. Lexical Tokens (词法标记)
以下规则用于定义底层词法分析器,该分析器向更高级别的解析器提供标记。本节定义了用于结构化头部字段体的标记。
注意: 本规范的读者需要特别注意这些词法标记在文档后面的低级和高级语法中是如何使用的。特别是,第3.2.2节中定义的空白标记和注释标记被用于此处定义的低级标记中,而这些低级标记又被用作后面定义的高级标记的一部分。因此,即使空白和注释可能不会显式出现在特定定义中,它们也可能被允许出现在高级标记中。
3.2.1. Quoted characters (引用字符)
某些字符被保留用于特殊解释,例如分隔词法标记。为了允许将这些字符用作未解释的数据,提供了引用机制。
quoted-pair = ("\" (VCHAR / WSP)) / obs-qp
无论在哪里出现quoted-pair,它都应被解释为单独的字符。也就是说,作为quoted-pair一部分出现的""字符在语义上是"不可见的"。
注意: ""字符可能出现在不属于quoted-pair的消息中。不出现在quoted-pair中的""字符在语义上不是不可见的。本规范中quoted-pair当前出现的唯一位置是ccontent、qcontent和第4节中的obs-dtext。
示例:
引用的反斜杠: "\\" → 解释为: \
引用的引号: "\"" → 解释为: "
引用的空格: "\ " → 解释为: (空格)
3.2.2. Folding White Space and Comments (折叠空白和注释)
空白字符,包括折叠中使用的空白 (在第2.2.3节中描述),可能出现在头部字段体中的许多元素之间。此外,被视为注释的字符串可能作为括号中包含的字符包含在结构化字段体中。以下定义了折叠空白 (FWS, Folding White Space) 和注释构造。
用括号括起来的字符串被视为注释,只要它们不出现在第3.2.4节中定义的"quoted-string"内。注释可以嵌套。
本规范中有几个地方可以自由插入注释和FWS。为了适应该语法,为注释和/或FWS可以出现的地方定义了一个额外的"CFWS"标记。但是,在本规范中出现CFWS的地方,禁止 (MUST NOT) 以这样的方式插入它,即折叠头部字段的任何行完全由WSP字符组成,而没有其他内容。
FWS = ([*WSP CRLF] 1*WSP) / obs-FWS
; Folding white space
ctext = %d33-39 / ; Printable US-ASCII
%d42-91 / ; characters not including
%d93-126 / ; "(", ")", or "\"
obs-ctext
ccontent = ctext / quoted-pair / comment
comment = "(" *([FWS] ccontent) [FWS] ")"
CFWS = (1*([FWS] comment) [FWS]) / FWS
在整个规范中,FWS (折叠空白标记) 出现的地方,它表示可以进行第2.2.3节中讨论的折叠的地方。无论在消息中哪里出现折叠 (即,包含CRLF后跟任何WSP的头部字段体),在根据本规范对该头部字段进行任何进一步的语义分析之前,都会执行展开 (删除CRLF)。也就是说,FWS中出现的任何CRLF在语义上都是"不可见的"。
注释通常用于结构化字段体中以提供一些人类可读的信息文本。由于注释允许包含FWS,因此允许在注释内进行折叠。还要注意,由于注释中允许使用quoted-pair,因此括号和反斜杠字符可能出现在注释中,只要它们作为quoted-pair出现即可。从语义上讲,封闭的括号不是注释的一部分; 注释是包含在两个括号之间的内容。如前所述,任何quoted-pair中的""和注释中出现的任何FWS中的CRLF在语义上都是"不可见的",因此也不是注释的一部分。
结构化头部字段中词法标记之间出现的FWS、注释或CFWS的运行在语义上被解释为单个空格字符。
注释示例:
From: Pete (A nice \) chap) <[email protected]>
↑ ↑
└── 注释开始 └── 转义的括号
解析后的显示名: Pete
实际邮箱: [email protected]
3.2.3. Atom (原子)
结构化头部字段体中的若干产生式只是某些基本字符的字符串。此类产生式称为原子 (Atoms)。
某些结构化头部字段体还允许在atext的运行中使用句点字符 (".", ASCII值46)。为此目的定义了一个额外的"dot-atom"标记。
atext = ALPHA / DIGIT / ; Printable US-ASCII
"!" / "#" / ; characters not including
"$" / "%" / ; specials. Used for atoms.
"&" / "'" /
"*" / "+" /
"-" / "/" /
"=" / "?" /
"^" / "_" /
"`" / "{" /
"|" / "}" /
"~"
atom = [CFWS] 1*atext [CFWS]
dot-atom-text = 1*atext *("." 1*atext)
dot-atom = [CFWS] dot-atom-text [CFWS]
specials = "(" / ")" / ; Special characters that do
"<" / ">" / ; not appear in atext
"[" / "]" /
":" / ";" /
"@" / "\" /
"," / "." /
DQUOTE
atom和dot-atom都被解释为单个单元,包含组成它的字符串。从语义上讲,围绕其余字符的可选注释和FWS不是atom的一部分; atom只是atom中的atext字符的运行,或dot-atom中的atext和"."字符。
示例:
atom示例:
- john
- example
- user_name
- info+tag
dot-atom示例:
- john.doe
- first.middle.last
- [email protected] (用于邮箱本地部分)
注意: "specials"标记不会出现在本规范的其他任何地方。它只是不出现在atext中的可见 (即,非控制、非空白) 字符。提供它只是因为它对于使用词法分析消息的工具的实现者很有用。specials中的每个字符都可以用于指示词法分析中的标记化点。
3.2.4. Quoted Strings (引用字符串)
包含原子中不允许的字符的字符串可以用引用字符串格式表示,其中字符被引号 (DQUOTE, ASCII值34) 字符包围。
qtext = %d33 / ; Printable US-ASCII
%d35-91 / ; characters not including
%d93-126 / ; "\" or the quote character
obs-qtext
qcontent = qtext / quoted-pair
quoted-string = [CFWS]
DQUOTE *([FWS] qcontent) [FWS] DQUOTE
[CFWS]
quoted-string被视为一个单元。也就是说,quoted-string在语义上与atom相同。由于quoted-string允许包含FWS,因此允许折叠。还要注意,由于quoted-string中允许使用quoted-pair,因此引号和反斜杠字符可能出现在quoted-string中,只要它们作为quoted-pair出现即可。
从语义上讲,引号字符外的可选CFWS和引号字符本身都不是quoted-string的一部分; quoted-string是包含在两个引号字符之间的内容。如前所述,任何quoted-pair中的""和quoted-string中出现的任何FWS/CFWS中的CRLF在语义上都是"不可见的",因此也不是quoted-string的一部分。
示例:
"Joe Q. Public" → Joe Q. Public
"First Last" → First Last
"Giant; \"Big\" Box" → Giant; "Big" Box
"包含\空格的\名字" → 包含 空格的 名字
3.2.5. Miscellaneous Tokens (其他标记)
定义了三个额外的标记: word和phrase用于atom和/或quoted-strings的组合,unstructured用于非结构化头部字段和结构化头部字段中的某些地方。
word = atom / quoted-string
phrase = 1*word / obs-phrase
unstructured = (*([FWS] VCHAR) *WSP) / obs-unstruct
示例:
word:
- john (atom)
- "Joe Public" (quoted-string)
phrase:
- John Doe
- "Joe Public" <[email protected]>
- Dr. Jane "The Expert" Smith
unstructured:
- This is any free-form text
- 任意内容 with symbols!@#$
3.3. Date and Time Specification (日期和时间规范)
日期和时间值出现在多个头部字段中。本节指定完整日期和时间规范的语法。尽管整个date-time规范中都允许使用折叠空白,但推荐 (RECOMMENDED) 在FWS出现的每个位置 (无论是必需的还是可选的) 使用单个空格; 一些较旧的实现不会正确解释较长的折叠空白序列。
date-time = [ day-of-week "," ] date time [CFWS]
day-of-week = ([FWS] day-name) / obs-day-of-week
day-name = "Mon" / "Tue" / "Wed" / "Thu" /
"Fri" / "Sat" / "Sun"
date = day month year
day = ([FWS] 1*2DIGIT FWS) / obs-day
month = "Jan" / "Feb" / "Mar" / "Apr" /
"May" / "Jun" / "Jul" / "Aug" /
"Sep" / "Oct" / "Nov" / "Dec"
year = (FWS 4*DIGIT FWS) / obs-year
time = time-of-day zone
time-of-day = hour ":" minute [ ":" second ]
hour = 2DIGIT / obs-hour
minute = 2DIGIT / obs-minute
second = 2DIGIT / obs-second
zone = (FWS ( "+" / "-" ) 4DIGIT) / obs-zone
规则说明:
- day: 月份的数字日期
- year: 任何1900年或更晚的数字年份
- time-of-day: 指定自指定日期午夜以来的小时数、分钟数和可选的秒数
- date和time-of-day: 应该 (SHOULD) 表示本地时间
时区 (zone) 指定date和time-of-day所表示的相对于协调世界时 (UTC, Coordinated Universal Time,以前称为"格林威治标准时间") 的偏移量:
- "+"或"-"表示time-of-day是在 (即,东边) 还是在 (即,西边) 世界时之前
- 前两位数字表示与世界时的小时差
- 后两位数字表示与世界时的额外分钟差
+hhmm表示+(hh * 60 + mm)分钟-hhmm表示-(hh * 60 + mm)分钟+0000应该 (SHOULD) 用于表示世界时的时区-0000也表示世界时,但用于表示时间是在可能处于世界时以外的本地时区的系统上生成的,并且date-time不包含有关本地时区的信息
date-time规范必须 (MUST) 在语义上有效:
- day-of-week (如果包括) 必须 (MUST) 是日期所暗示的那一天
- 数字day-of-month必须 (MUST) 在1和指定月份允许的天数之间 (在指定年份中)
- time-of-day必须 (MUST) 在00:00:00到23:59:60范围内 (秒数允许闰秒; 参见 [RFC1305])
- zone的后两位数字必须 (MUST) 在00到59范围内
日期时间示例:
完整格式:
Date: Fri, 21 Nov 1997 09:55:06 -0600
Date: Mon, 20 Dec 2025 10:00:00 +0800
无星期:
Date: 21 Nov 1997 09:55:06 -0600
UTC时间:
Date: 21 Nov 1997 15:55:06 +0000
无本地时区信息:
Date: 21 Nov 1997 15:55:06 -0000
时区对照表:
| 时区格式 | 含义 | 示例 |
|---|---|---|
+0800 | UTC+8 (北京时间) | 10:00 +0800 = 02:00 UTC |
-0500 | UTC-5 (美东标准时间) | 10:00 -0500 = 15:00 UTC |
+0000 | UTC (明确) | 10:00 +0000 = 10:00 UTC |
-0000 | UTC (无时区信息) | 系统可能不在UTC |
3.4. Address Specification (地址规范)
地址出现在多个消息头部字段中,用于指示消息的发送者和接收者。地址可以是单个邮箱 (Mailbox),也可以是邮箱组 (Group)。
address = mailbox / group
mailbox = name-addr / addr-spec
name-addr = [display-name] angle-addr
angle-addr = [CFWS] "<" addr-spec ">" [CFWS] /
obs-angle-addr
group = display-name ":" [group-list] ";" [CFWS]
display-name = phrase
mailbox-list = (mailbox *("," mailbox)) / obs-mbox-list
address-list = (address *("," address)) / obs-addr-list
group-list = mailbox-list / CFWS / obs-group-list
邮箱 (Mailbox) 接收邮件。它是一个概念实体,不一定与文件存储有关。例如,某些站点可能选择在打印机上打印邮件并将输出投递到收件人的桌面。
通常,邮箱由两部分组成:
- 可选的显示名 (Display Name): 指示可以向邮件应用程序的用户显示的收件人 (可以是人或系统) 的名称
- 用尖括号括起来的addr-spec地址: 用
<和>包围
还有一种邮箱的替代简单形式,其中addr-spec地址单独出现,没有收件人姓名或尖括号。互联网addr-spec地址在第3.4.1节中描述。
注意: 某些遗留实现使用简单形式,其中addr-spec出现时不带尖括号,但将收件人姓名作为注释放在addr-spec后面的括号中。由于注释中信息的含义未指定,因此实现应该 (SHOULD) 使用邮箱的完整name-addr形式,而不是遗留形式,以指定与邮箱关联的显示名。此外,由于某些遗留实现会解释注释,因此通常应该不 (SHOULD NOT) 在地址字段中使用注释,以避免混淆此类实现。
当希望将多个邮箱视为单个单元 (即,在分发列表中) 时,可以使用组构造。组构造允许发送者指示一个命名的收件人组。这通过为组提供显示名,后跟冒号,然后是任意数量 (包括零和一) 邮箱的逗号分隔列表,并以分号结束来完成。由于邮箱列表可以为空,因此使用组构造也是一种向收件人传达消息已发送给一个或多个命名收件人集的简单方法,而无需实际提供这些收件人的任何单独邮箱地址。
邮箱地址示例:
简单形式 (仅地址):
[email protected]
完整形式 (带显示名):
Alice Smith <[email protected]>
"Joe Q. Public" <[email protected]>
多个收件人:
To: [email protected], [email protected]
To: Alice <[email protected]>, Bob <[email protected]>
组地址:
To: Development Team: [email protected], [email protected];
Cc: Undisclosed recipients:;
3.4.1. Addr-Spec Specification (地址规范详述)
addr-spec是一个特定的互联网标识符,包含本地解释的字符串,后跟at符号字符 ("@", ASCII值64),然后是互联网域 (Domain)。本地解释的字符串是dot-atom或quoted-string。如果字符串可以表示为dot-atom (即,它除了atext字符或被atext字符包围的"."之外不包含其他字符),则应该 (SHOULD) 使用dot-atom形式,并且不应该 (SHOULD NOT) 使用quoted-string形式。注释和折叠空白不应该 (SHOULD NOT) 在addr-spec中的"@"周围使用。
注意: 此处给出了addr-spec域部分的自由语法。但是,域部分包含由其他协议使用和指定的地址信息 (例如, [RFC1034], [RFC1035], [RFC1123], [RFC5321])。因此,实现有责任使其使用的上下文的地址语法符合要求。
addr-spec = local-part "@" domain
local-part = dot-atom / quoted-string / obs-local-part
domain = dot-atom / domain-literal / obs-domain
domain-literal = [CFWS] "[" *([FWS] dtext) [FWS] "]" [CFWS]
dtext = %d33-90 / ; Printable US-ASCII
%d94-126 / ; characters not including
obs-dtext ; "[", "]", or "\"
域部分 (domain) 标识邮件投递到的点。在dot-atom形式中,这被解释为互联网域名 (主机名或邮件交换器名),如 [RFC1034], [RFC1035]和 [RFC1123] 中所述。在domain-literal形式中,域被解释为特定主机的字面互联网地址。在这两种情况下,如何使用地址以及如何将消息传输到特定主机都在单独的文档中涵盖,例如 [RFC5321]。这些机制超出了本文档的范围。
本地部分 (local-part) 是依赖于域的字符串。在地址中,它只是在特定主机上被解释为特定邮箱的名称。
地址示例:
标准格式:
[email protected]
[email protected]
[email protected]
带引用的本地部分:
"joe smith"@example.com
"admin@site"@example.com
域字面值 (IP地址):
user@[192.0.2.1]
postmaster@[IPv6:2001:db8::1]
3.5-3.6节说明
本文档中第3.5节(Overall Message Syntax)和第3.6节(Field Definitions)定义了完整的消息结构和各个头部字段的详细语法。这些部分包含:
- 3.5: 消息的总体语法结构
- 3.6: 所有头部字段的详细定义
- 3.6.1: Date字段
- 3.6.2: From, Sender, Reply-To字段
- 3.6.3: To, Cc, Bcc字段
- 3.6.4: Message-ID, In-Reply-To, References字段
- 3.6.5: Subject, Comments, Keywords字段
- 3.6.6: Resent字段
- 3.6.7: Trace字段(Received, Return-Path)
- 3.6.8: 可选字段
这些详细的语法定义对于实现邮件解析器和生成器至关重要。完整的ABNF语法和详细说明请参阅RFC 5322官方文档。
第3章总结
关键语法元素
消息结构层次:
消息 (Message)
├── 词法标记 (Lexical Tokens)
│ ├── atom, quoted-string
│ ├── word, phrase
│ └── comment, FWS
├── 日期时间 (Date-Time)
│ └── Day, DD Mon YYYY HH:MM:SS +ZZZZ
└── 地址 (Address)
├── mailbox: name <user@domain>
└── group: name: addr1, addr2;
ABNF语法规则应用
- 生成消息: 忽略
obs-规则,使用严格语法 - 解析消息: 包含
obs-规则,兼容废弃格式 - 语义解释: CFWS和引用字符"不可见"
实现检查清单
- 正确处理折叠空白 (FWS)
- 支持注释 (嵌套括号)
- 解析引用字符串和引用对
- 验证日期时间格式和有效性
- 解析邮箱地址和组地址
- 处理本地部分和域部分
- 兼容废弃语法 (仅解析,不生成)