6.6 规范的编码模式(Canonical Encoding Model)
何时将邮件数据转换为标准形式并编码、这个过程如何处理CRLF(新行符,并且在不同的系统中具有不同的形式)、传输编码与字符集之间有什么关系――在以前版本的RFC中,关于这些方面的问题都很混乱。由于这个原因,RFC2049给出了编码的规范模型。
6.7 Quoted-Printable编码
Quoted-Printable编码适用于内容多为US-ASCII字符集中可打印字符的情况。经过它编码的数据不再需要邮件传输系统进行转换。如果被编码的数据多为US-ASCII字符,则编码后的内容会保留那些可人为识别的部分。完全由US-ASCII字符构成的内容也可以进行Quoted-Printable编码,以确保可以通过字符转换及(或)行封装的网关来传递所有的消息数据。
在这种编码方式中,字节按如下规则描述:
(1) (普通的8位字节描述)除了被编码内容中标准换行符CRLF序列里的CR和LF字节之外的任何字节,都应该被表示成“=”后面紧跟着两个表示字节值的十六进制数字的形式。此处使用的十六进制字符表是“0123456789ABCDEF”。必须使用大写字母,而不允许使用小写字母。因此,十进制的值12可以被表示为:“=0C”,十进制值61(表示US-ASCII字符集中的等号)可以被表示为“=3D”。除了选择后面规则中所规定的编码方式之外,必须遵循本规则。
(2) (文字表示法) 十进制值从33到60、从62到126的字节,可以直接表示为US-ASCII字符集中的相应字符(即从感叹号‘!’到小于号‘<’、从大于号‘>’到符号‘~’)。
(3) (空格) 十进制值为9和 32的字节可以被分别表示为US-ASCII字符集中的TAB(HT)和空格(SPACE)。但是这种表示方法不可以应用在编码行的末尾。在编码后的内容中,任何TAB(HT)及空格(SPACE)字符后面都必须跟随有可打印的字符。特别的,行末的“=”表示“软换行”(见规则5),它可以跟随在一个或多个TAB(HT)或空格(SPACE)字符后面。它遵循了这样一条规则:当行末的最后一个字节的值在9到32之间时,必须按照规则(1)进行编码。这是必要的,因为一些传送消息的MTA(Message Transport Agent--消息传送代理:将消息从一个用户传送至另一个用户处或进行其中一部分工作的程序)会用空白字符来填充行,而其它的则会从行末移除空白字符。因此,当进行“Quoted-Printable”解码时,要删除行末的任何空白字符, 因为它们很可能是被中间的传送代理加上的。
(4) (换行符) Quoted-Printable编码将文本内容中的换行符(CRLF序列)表示成RFC 822的换行符(也是CRLF序列)。因为,除了文本类型(text)之外的其它规范媒体类型通常都不会包含有换行符CRLF序列。这些媒体类型的Quoted-Printable编码中不会出现硬换行符(就是有意义的、要显示给用户的换行符)。所以,在Quoted-Printable编码的非文本类型的数据中就可能会出现“=0D”、“=0A”、“=0A0D”、“=0D0A”等序列。
注意,许多实现机制都是直接将不同的媒体类型编码为本地形式,而不是先将它们转换成规范的格式然后编码,最后再转换成本地形式。特别的,在不使用CRLF序列做为行终止符的系统中,当对纯文本内容进行操作时,会出现这种情况。而只有在某种组合的规范编码等价于分别执那三步操作的时候,才允许执行这种优化的操作。
(5) (软换行符) Quoted-Printable编码规则要求每个编码行的长度不超过76字节。如果需要编码更长的行,就必须要使用“软”换行符。在编码内容中,出现在一行最后位置的等号(“=”)表示无意义的换行符(软换行符)。
因此,如果编码前的格式是一个单独的未编码行:
Now's the time for all folk to come to the aid of their country.
那么在Quoted-Printable编码后,它可以被表示为:
Now's the time =
for all folk to come=
to the aid of their country.
通过这种机制,过长的行可以被编码成一种能被用户代理存贮的形式。76个字符的限制不包含行末的CRLF序列,但是计算了其它的全部内容,包括所有的等号。
因为在Quoted-Printable中,连字符(“-”)可以不编码,所以当Quoted-Printable编码的内容被包含在一个或多个multipart实体中时,一定要注意不能让边界分隔符(boundary delimiter)出现在编码内容的任何位置上。(选择边界分隔符时,最好使其包含一个“=_”序列,因为这个序列永远不会出现在Quoted-Printable编码内容中。参考RFC2046中关于multipart的定义)
注意:Quoted-Printable编码为传输中数据的可读性及可靠性提供了一种折中的方法。通过Quoted-Printable编码的主体(body)能可靠的在多数的邮件网关上传输,但是在少数网关上--特别是在涉及EBCDIC转换的网关上--它也许不能很好的工作。base64编码方式提供了更高的可信度。另外一个能经过EBCDIC网关进行可靠传输的方法,就是将如下US-ASCII字符按照规则(1)进行编码:
!"#$@[\]^`{|}~
quoted-printable数据内容都被假定为面向行的,因此可以预料,在传输过程中可能会转换各行之间的换行符。也就是说,在不同换行习惯的系统间传输的纯文本邮件通常被转换成因特网邮件。如果这种转换会构成错误的数据,那么就需要采用base64而不是quoted-printable来进行编码。
注释:quoted-printable编码规则决定了不能生成几种类型的子串,因此,如果quoted-printable编码器输出这些字符串,则说明它们是非法的。本注释列举了这些情况以及在解码过程中处理这些非法子串的方法。
(1) 等号“=”后而跟着两个十六进制的数字,但两个数字或其中的一个是小写字母“abcdef”的非法格式。一个健壮的执行程序可以选择将它们当做相应的大写字母来识别。
(2) 如果等号“=”后面的字符即不是十六进制数字(包括“abcdef”),也不是CRLF对中的CR字符,那么就是非法的。产生这种情况可能是因为将未编码的US-ASCII文本包含进了消息中的quoted-printable编码部分。对于健壮的执行程序来说,一个合理的解决方式是将等号及其后面的字符包含进解码内容中,而不进行任何转换。而且如若可能,要向用户指出:在这个位置处的解码也许会出现错误。
(3) 等号“=”不可以是编码内容中的最后一个或倒数第二个字符。相应的处理方式可以参考上面的情况(2)。
(4) 除TAB字符、CRLF对中的CRLF字符之外,不可以出现其它的控制字符。同样也不可以存在十进制值大于126的字节。如果解码时在输入数据中出现这些字符,则健壮的执行程序要将这些非法字符从解码数据中删除,并要警告用户发现非法字符。
(5) 编码后的行长度不可以超过76字符(不包括CRLF换行符)。对于一个健壮的解码程序而言,如果在解码时从输入的编码内容中发现了更长的行,则仍然要对其进行解码,同时还可以向用户报告编码错误。
对实现者的警告:如果用quoted-printable编码二进制数据,则必须要分别将CR和LF字符编码为“=0D”和“=0A”。特别的,应该将二进制数据中的CRLF序列编码为“=0D=0A”。否则,如果CRLF序列被描述成一个硬换行符,那么在使用其它换行符的系统中解码数据时就会出错。
按照如下语法描述quoted-printable数据:
quoted-printable := qp-line *(CRLF qp-line)
qp-line := *(qp-segment transport-padding CRLF)
qp-part transport-padding
qp-part := qp-section
; 最大长度为76字符
qp-segment := qp-section *(SPACE / TAB) "="
; 最大长度为76字符
qp-section := [*(ptext / SPACE / TAB) ptext]
ptext := hex-octet / safe-char
safe-char := <any octet with decimal value of 33 through
60 inclusive, and 62 through 126>
; 也不推荐使用
; 没有在RFC2049中列为“mail-safe”的字符
hex-octet := "=" 2(DIGIT / "A" / "B" / "C" / "D" / "E" / "F")
; 这些字节被用来表示值大于127的字节,
; 以及出现在行尾的空格或TAB。而且,
; 对于没有在RFC2049中列为“mail-safe”
; 的字符,也推荐使用这种表示方式。
transport-padding := *LWSP-char
; 设计者不可以产生非零长度的填充,
; 但是接收者必须能够处理由传输机构
; 增加的填充内容。
注意:本BNF中所提到的附加LWSP是不被承认的,因为这个BNF并没有详细说明一个结构头字段。