[转载]多用途Internet邮件扩展(MIME)规范说明:消息体的格式
信息来源:邪恶八进制信息安全团队([url]www.eviloctal.com[/url])<BR><BR>1. 介绍<BR><BR>通常的邮件格式于1982年出现以来,它已经被广为使用,在使用的过程中,一些不足之处也开始暴露出来。通常的电子邮件是一种文本格式的消息,这样一来,一些非文本的信息,如多媒体信息就无法传送;就算在文本方面,这种文件格式在传送非ASCII码的文本时也有些不足。<BR><BR>原来邮件协议的一个重大的不足在于,它限制文本的内容是一行内容相应比较短的7位ASCII码。这带来了许多麻烦。这个不足在网关允许在通常的电子邮件主机和X.400主机之间通信后变得更加突出,X.400指定了一种发送非文本内容电子邮件的机制,将X.400发向通常的电子邮件主机时,要么把内容转换成IA5Text格式,或者抛弃,并同时通知接收者有信息在传送中被丢弃。<BR><BR>本文主要描述一些机制可以解决上述问题,特点地它描述了MIME版本头域,内容类型头域,内容传送编码头域和用于描述文件内容的两个头域。在阅读此文档时可能有些地方您会觉得非常怪,这是为了照顾到和不同的版本进行兼容而不得不这样。<BR><BR>2. 定义、惯例和基本BNF语法<BR><BR>本文中的所有参数和名称均是大小写敏感的。对于BNF语法不熟悉的请参阅其它资料。<BR><BR>2.1. CRLF<BR><BR>CRLF指回车和换行的组合,也就是十进制13加上10的组合。如果不熟悉ASCII码,请您自己查询有关资料。这里不再详细说明了。<BR><BR>2.2. 字符集<BR><BR>用在MIME中的字符集指的是将和一种字符序列转换为另一种字符序列的方法。请注意:这里指的是单向的转换,不需要将转换过的序列再无误地转换回来。这种定义允许不同的字符编码用作字符集,但是与MIME字符集名相关的定义必须完全指定所要进行的转换(映射)操作。<BR><BR>2.3. 消息<BR><BR>在没有特别说明的它指文件内容包括的内容。<BR><BR>2.4. 实体<BR><BR>实体指的是MIME定义的域和消息的内容或多部分实体的内容的一部分。这是MIME的精华之所在。实体的内容通常称为体。域的任何类型都可以在实体中出现,但是只有以"content-"开头的才有与MIME相关的意义,这并不说明它们没有意思。<BR><BR>2.5. 体部分<BR><BR>体部分指的是在多部分实体内的一个实体。<BR><BR>2.6. 体<BR><BR>这个名词指的是一个实体的体部分。请注意:上面四个定义是循环的,这是不可避免的,因为MIME消息本身就是循环定义的。<BR><BR>2.7. 7bit数据<BR><BR>7bit数据指的是两个CRLF之间的字符数少于998,而且每个字符不大于127。回车和换行只用作分隔之用。<BR><BR>2.8. 8bit数据<BR><BR>7bit数据指的是两个CRLF之间的字符数少于998,而且每个字符可以大于127。回车和换行只用作分隔之用。<BR><BR>2.9. 二进制数据<BR><BR>二进制数据指不限制数据的大小。<BR><BR>2.10. 行<BR><BR>所谓行就是两个CRLF之间的字符序列。<BR><BR>3. MIME头域<BR><BR>MIME定义了一些新的头域用来描述MIME体的内容。这些头域发生在以下两种情况下:<BR><BR>(1) 作为通常邮件内容的一部分;<BR><BR>(2) 在一个MIME体部分头中; <BR><BR>以下是这些头域的通常定义:<BR><BR>entity-headers := [ 内容 CRLF ] [ 编码 CRLF ] [ id CRLF ] [ 描述CRLF ] *( MIME扩展域CRLF )<BR><BR>MIME-message-headers := entity-headers fields<BR><BR>版本 CRLF //BNF表示中的顺序在这里不起作用;<BR><BR>MIME-part-headers := entity-headers [ fields ] //BNF表示中的顺序在这里不起作用;未以"content-"开头的域忽略<BR><BR>不同的特定的MIME头域在下面会加以说明。<BR><BR>4. MIME版本头域<BR><BR>因为通常的邮件格式一直是作为邮件唯一的格式被使用的,因此也没有什么标准可言,这里说明的规范与通常的邮件格式没有什么关系,因此也就定义了一个新的头域"MIME-Version",它是用来声明使用的Internet消息体格式标准版本的。根据本文生成的文档必须包括这样的头域内容:<BR><BR>MIME-Version: 1.0<BR><BR>因为以后的标准可能会为本文所描述的标准有所扩展,所以版本域的格式如下定义:<BR><BR>version := "MIME-Version" ":" 1*DIGIT "." 1*DIGIT<BR><BR>如果接收到的版本号小于1.0,它所遵守的标准不是本文所描述的标准。这个头域必须包括在消息的头部,但是对于多部分体的每个体部分就不是非要不可了。本文不对未来的标准作出规定。MIME内可以包括不同格式的文件,这些格式可以有不同的版本,这些版本号和我们上面的版本号可不是一回事,这些版本号在媒介格式内指定,如果一定要指定媒介版本号,这些媒介格式可以用"version"参数指定。<BR><BR>实现者请注意:下面三种格式是等效的: <BR><BR>MIME-Version: 1.0<BR><BR>MIME-Version: 1.0 (produced by MetaSend Vx.x)<BR><BR>MIME-Version: (produced by MetaSend Vx.x) 1.0<BR><BR>MIME-Version: 1.(produced by MetaSend Vx.x)0<BR><BR>如果没有MIME版本域,接收方可以根据自己的方法解释消息体。但这样做可能导致文件内容显示时有问题,因为对方传送过来的可能是压缩文件,而直接压缩文件则是根本不可读的。<BR><BR>5. 内容类型头域<BR><BR>本域的目的就是使接收方能够正确识别内容作相应的操作把内容以合适的方式提交给用户。本域中的值称为媒介类型。<BR><BR>这个域指定实体内体的数据属性。它给出媒介类型和子类型标记,还提供一些辅助的参数信息,在媒介类型和子类型名后是一些参数,这些参数是以“属性=值”的形式给出,参数的顺序没有关系。<BR><BR>通常顶级媒介类型用于声明数据的通常类型,而子类型指出这种数据类型的特定格式。因此,如下格式的说明"image/xyz"足以向用户说明传送的数据是一幅图,既然用户根本不知道这个后面的XYZ是个什么东西也没有关系。这对于用户来说是很重要的,因为用户知道了类型以后可以根据类型采取相应的行动,不会把一幅图当文本显示出来。如果数据有多个类型,应该使用"multipart"或"application"声明。<BR><BR>参数并不影响类型的内容,是什么还是什么,因此参数集只有相对于某种特定的类型和子类型时才有意义。主类型和子类型都有自己的参数,有的参数是必须的,有的是可选的,MIME必须忽略不能识别的参数。例如:"charset"参数用于所有"text"的子类型,而"boundary"对于"multipart"类型的子类型是必须的。在MIME中没有可以用于所有类型的参数。<BR><BR>MIME的类型和每个类型下的子类型请参阅其它资料,这里就不一一说明了。只要大家注意一下,有两种混合类型需要特别处理一下。<BR><BR>顶级媒介类型是安全的,如果需要添加新的文件,只需要在相应的顶级类型下添加新的子类型即可。当然,未来可能会添加新的顶级类型,但那也是对本文档的扩展。如果用户真需要自己的顶级类型,请以"X-"开始以说明这不是一个标准的顶级类型。<BR><BR>5.1. 内容类型头域语法<BR><BR>content := "Content-Type" ":" type "/" subtype *(";" parameter)<BR><BR>//类型与子类型对大小写敏感;<BR><BR>type := discrete-type / composite-type<BR><BR>discrete-type := "text" / "image" / "audio" / "video" / "application" / extension-token<BR><BR>composite-type := "message" / "multipart" / extension-token<BR><BR>extension-token := ietf-token / x-token<BR><BR>ietf-token := <在IANA注册的扩展标记或由RFC定义的扩展标记><BR><BR>x-token := <以"X-"或"x-"开始的字符串,请注意在"X-"或"x-"后面不能有空格><BR><BR>subtype := extension-token / iana-token<BR><BR>iana-token := <标准定义的扩展标记><BR><BR>parameter := attribute "=" value<BR><BR>attribute := token //对大小写敏感;<BR><BR>value := token / quoted-string<BR><BR>token := 1*<任何ASCII码,除了空格控制字符和tspecials><BR><BR>tspecials := "(" / ")" / "<" / ">" / "@" / "," / ";" / ":" / "\" / <"> "/" / "[" / "]" / "?" / "=" <BR><BR>"tspecials"的定义比标准邮件协议中多了"/","?"和"=",少了".",另外,子类型不能为空,不存在默认的子类型。类型,子类型和参数对大小写不敏感。参数通常对大小写敏感。请注意:下面两个是完全等效的:<BR><BR>Content-type: text/plain; charset=us-ascii (Plain text)<BR><BR>Content-type: text/plain; charset="us-ascii"<BR><BR>这个语法中没有体现出来的限制就是,子类型名不能冲突。自己定义的类型名必须以"X-"开始,而且接收方和发送方都必须理解这个类型的意义是什么,而且能够做出适当的处理。<BR><BR>5.2. 内容类型默认值<BR><BR>默认的标准电子邮件信息(内容只是ASCII码)不以MIME内容类型头开始,这可以说成是内容的默认值吧,如果非要用MIME进行说明,那么说明如下:<BR><BR>Content-type: text/plain; charset=us-ascii<BR><BR>如果内容的规定有冲突也使用默认值,只有MIME版本头域而没有任何内容类型头域时,接收方也认为它是普通的ASCII字符集信息。如果没有MIME版本时,也要使用默认值。<BR><BR>6. 内容传送编码头域<BR><BR>许多可以通常电子邮件传送的媒介类型可以以8bit字符或二进制数据传送。这样的数据可以在一些传输协议上进传送。我们应该注意,通常的电子邮件协议(SMTP)限制邮件内容为7位数据,而且每行不得多于1000个字符,每行必须以CRLF结束。<BR><BR>因为有必须定义一种机制用于将类似的数据编制为7位短行格式。正确标准未编码的信息为限制较少的格式(以供直接传送)也十分必要。本文说明“内容传送编码”头域达到这一功能。<BR><BR>6.1. 内容传送编码语法<BR><BR>encoding := "Content-Transfer-Encoding" ":" mechanism<BR><BR>mechanism := "7bit" / "8bit" / "binary" / "quoted-printable" / "base64" / ietf-token / x-token<BR><BR>以上的数值不区别大小写。7位编码是默认值。<BR><BR>6.2. 内容传送编码语义<BR><BR>这个单独的内容传送编码实际上提供两种信息,它指出内容进行了什么编码,也指出了解码应该进行什么操作。请注意:对有效编码方式解码器没有什么限制。现在定义了三种传送方式:标记式编码,"quoted-printable"编码和"base64"编码。域有二进制,8位和7位三种。<BR><BR>内容传送编码值7位,8位和二进制都指使用标记式编码传送方式。因此,它们说明了体数据大概是什么样的东西,同时也提供了关于在给定传送系统中传送的所需要的编码类型。quoted-printable类型和base64类型传送的数据编码属于7位范围,这使得这两种编码方式能够在要求严格的传送协议上进行传送。<BR><BR>必须使用正确的内容传送编码标记。不能把包括8位数据的信息标记为7位,也不能标记为其它类型(二进制除外)。在编码的时候要注意传送效率和可读性的平衡。因为这个原因,需要两种编码方式:quoted-printable编码和base64编码方式。原来的邮件中不能包括二进制数据,但是随着包括MIME在内的其它邮件传送方式的广泛使用,二进制数据已经进入了邮件中。这也就是我们现在为什么能够使用电子邮件传送图画和其它二进制文件的原因了。<BR><BR>注意:内容传送编码域中的值不代表内容是什么东西,它只表示对内容的编码方式或传送系统要求的编码方式。<BR><BR>6.3. 新内容传送编码<BR><BR>如果用户需要实现自己规定的编码方式,必须在前面加上"X-",表明这不是一个标准的编码方式,不要轻易使用自己规定的编码方式。<BR><BR>6.4. 解释与使用<BR><BR>如果内容传送头域作为消息头的一部分,那它对整个消息体起作用;如果它只在实体中的头中出现,它的作用域就只是这个实体。如果实体的是"multipart"类型,内容传送头域只能是"7bit","8bit"或"binary"三种。<BR><BR>应该注意大部分的媒介类型以字节定义而不是以位定义,所以这里定义的编码方式均是以面向字节的,而不是面向位的。如果需要通过这些机制编码位流,必须先转换为8位字节流,不足8位的要以填充位填充。本文中默认的编码方式是对ASCII码的,所以当您看到下面的叙述:<BR><BR>Content-Type: text/plain; charset=ISO-8859-1<BR><BR>Content-transfer-encoding: base64<BR><BR>它指的是源文件是ISO-8895-1类型的,而编码方式为base64。内容传送编码和媒介类型有搭配关系,不要混用,特别是对于混合型文件不要便用除"7bit","8bit"或"binary"以外的编码方式。<BR><BR>不要让只需要7位编码的数据应用8位编码对传送系统造成不必要的负担,也不要把本应该以8位编码编码的数据应用了7位编码,这样就会造成数据错误。如果内容传送编码不可理解,一般它会被看作"application/octet-stream"。<BR><BR>6.5. 转换编码<BR><BR>quoted-printable和base64可以相互转换,转换时唯一应该考虑的问题就是在quoted-printable中的回车换行,quoted-printable中的回车换行必须转换为标准的base64回车换行;同样,base64中的回车换行必须转换为quoted-printable中的回车换行。<BR><BR>6.7. Quoted-Printable内容传送编码<BR><BR>Quoted-Printable编码的大部分和用于表示的数据一般和可打印的ASCII码数据差不多。这种编码方式可以使信件内容不会邮件传送机制更改。如果数据被编码后仍然大部分是ASCII码,那么人还能够认出大部分内容。<BR><BR>在这种编码方式中有以下规则:<BR><BR>(1) (通常8位表示)任何字节(除了回车符和换行符)都可以用"="加上字节的数值加以表示。应该使用十六进制数表示:"0123456789ABCDEF",请注意,一定要使用大定字母。除了以下两种情况,通常情况下都应用这个规则。<BR><BR>(2) (直接表示)如果字节的十进制数值在33至60,或在62至126之间,可以直接以数数值相等的ASCII码表示。<BR><BR>(3) (空格类)ASCII码9和32分别是制表符(TAB)和空格(SPACE),这些字符不能直接在编码行中出现。它们必须跟着一个可打印的字符。特别的,在编码行未尾的"="指示一个软回画,在其后可以跟一个或多个TAB或SPACE。其它情况下对空格类字符的编码请遵守规则(1)进行,这是因为有些MTA(消息传送代理)可能把句子未尾的空格和其它空格类字符给删除了。<BR><BR>(4) (回车类)文本中的硬回车符在Quoted-Printable编码中也有。因为除了文本以外其它的媒介一般不会用CRLF当回车符。<BR><BR>(5) (软回车类)Quoted-Printable编码要求编码行不的长度不得多于76个字符,如果编码行实在太长,必须使用软回车。看看下面的例子:<BR><BR>如果源数据为:Now's the time for all folk to come to the aid of their country.那么编码出的数据应该为: <BR><BR>Now's the time =<BR><BR>for all folk to come=<BR><BR>to the aid of their country.<BR><BR>76个字符中不包括尾部的CRLF,但是包括编码行中的等号。<BR><BR>因为连线符可以自已代表自己,而边界标记中也有这个东西,所以一定要小心,不要让用户方误以为体中的一个连线符"-"是边界标记。<BR><BR>请注意:quoted-printable编码方式是在可读性和可靠的传送之间取了一个平衡,它在大部的邮件网关上工作正常,但是有一些邮件网关却不行,如EBCDIC网关,为了能够与这些网关一起正常工作,最好把“!"#$@[\]^`{|}~”应用规则1进行编码。因为quoted-printable数据是面向行的,如果不同的邮件系统有不同的表示回车换行的标准的话,就要小心了,如果确实因为回车换行的问题破坏了数据,最好使用base64编码。<BR><BR> <BR><BR>对于二进制数据应用quoted-printable编码,要特别注意CRLF和CR,LF的问题。还应该注意对方平台上对CRLF的解释规则。下面列出了quoted-printable数据的语法: <BR><BR>quoted-printable := qp-line *(CRLF qp-line)<BR><BR>qp-line := *(qp-segment transport-padding CRLF) qp-part transport-padding<BR><BR>qp-part := qp-section //最大长度76个字符;<BR><BR>qp-segment := qp-section *(SPACE / TAB) "=" //最大长度76个字符;<BR><BR>qp-section := [*(ptext / SPACE / TAB) ptext]<BR><BR>ptext := hex-octet / safe-char<BR><BR>safe-char := <数值在33至60,62至126之间的字符><BR><BR>hex-octet := "=" 2(DIGIT / "A" / "B" / "C" / "D" / "E" / "F")<BR><BR>transport-padding := *LWSP-char<BR><BR>不允许在元素之间加入LWSP。<BR><BR>6.8. Base64内容传送编码<BR><BR>Base64内容传送编码所应用的数据通常不是让人直接读的,而编码和解码的算法一般也比较简单,编码后的数据比编码前大33%。它使用了ASCII码的子体,用6位代表可打印字符,第65个字符"="用于指定特定的处理函数。请注意,这个子集有一个重要的特点,它在所有版本的ISO 646中都是一样的。有一些流行的编码方式如Macintosh的binhex 4.0和base85编码没有这个性质。<BR><BR>24位的输入组经编码后成为4个字符的输出。从左至右,24位的输入可以由3个8位的输入组合并而成。这24为被当作4个6位的组,每个组的数据根据base64字母对应表转换一个数。当位流通过base64编码后,位流变为最高位优先,也就是说,流中的第一个位成为8位字节中的最高位,而第8位则成为第一个8位字节的最低位。<BR><BR>表1:Base64字母对应表<BR><BR>[attach]2514[/attach] <BR><BR>编码输出流中行的长度不得大于76个字符。所有回车换行符或其它未在上表中定义的字符会被解码软件忽略。如果在编码中确实出现了不属于上表中的数据,这说明传送中有错误,要给出警告信息,或者(在某种情况下)拒绝接收。<BR><BR>当输入少于24位时,会有一个特殊的处理。0位被添加到最右端,以形成完整的6位组。使用“=”在尾部添加数据。因为所有的base64数据都是完整的8位,所以只会发生以下情况:(1)输入是24的倍数,则输出将是4个字符的倍数,这时不需要用“=”添加。(2)输入的最尾是8位,则输出的最尾是两个字符外加两个“=”添加符。(3)输入的最尾是16位,则输出的最尾是3个字符,外加一个“=”添加符。<BR><BR>因为“=”只用于添加数据,所以“=”的出现可以被看作到了数据的最末。但是也可能没有这种情况,如成熟传输的数据是3的倍数。<BR><BR>任何不属于上面表中字符的数据不被认为是base64编码数据。在文本没有被转换为标准格式前对它应用base64编码时要注意对回车换行的处理。文本中的回车换行符应该在进行编码前转换为CRLF。另外请注意:在base64编码中不必关心连字符,因为编码中根本不会出现连字符。<BR><BR>7. 内容ID头域<BR><BR>在建立高层用户代理时,可能希望体之间产生关联,因此体可以使用内容ID头域,它的语法如下:<BR><BR>id := "Content-ID" ":" msg-id<BR><BR>象消息ID值一样,它必须是全世界唯一的。内容ID值在代理服务器缓存体数据时特别有用,虽然它的应用是可选的,但是如果MIME媒介类型是"message/external-body"的话,它是必选的。应该特别注意的是内容ID值对multipart/alternative类型有特殊的语义。<BR><BR>8. 内容描述头域<BR><BR>使给定的体附加一些描述信息是很有好处的,于是有了这个可选的内容描述头域,它的语法如下:<BR><BR>description := "Content-Description" ":" *text<BR><BR>一般来说,描述内容只能是ASCII码,当然也可以是非ASCII内容(要当心有的系统不支持)。<BR><BR>9. 附加MIME头域<BR><BR>在将来可以会定义新的MIME头域,任何新的头域必须以"Content-"开始。<BR><BR>MIME-extension-field := <以"Content-"开始的任何头域>页:
[1]