2007年1月26日

一篇比较全的gnus编码介绍文章

Gnus 是构建在 Emacs 之上的,自然也就利用了 Emacs 超强的多国语言编码支持。所以如果你有一封乱码的邮件,如果 Gnus 看不了的话,那么估计卖糕的也不会有办法了。

为了处理各种编码的邮件,gnus 提供了很多可以设置的变量。不过变量多了也会带来麻烦,如果你设置不好的话,就有可能开枪打中自己的脚。

** 先谈谈邮件编码

邮件可以分为两个部分: header 和 body。比较文明的邮件会在 header 的`Content-Type' 这一行中用 MIME 说明邮件采用了什么编码。而不文明的邮件则缺少这种说明,对于只有英文的邮件,这样做不会有问题,而对于包含中文的邮件,就有可能这是导致乱码,小比尔的 Outlook Express 就喜欢发出这种邮件,一些webmail 也经常干这种事。

另外,在邮件头中,还可以指定邮件的传输编码。如果邮件的内容都是7位的us-ascii字符,就不会出现什么传输编码的问题,但是因为汉字的编码一般都是8位的,而有些邮件网关只能传输7bit字符,最高位作其它用途了,所以就出现了base64, quoted-printable 等把8位字符转换成7位编码进行传输的编码方案。邮件的传输编码可以在邮件头中用 `Content-Transfer-Encoding' 指出来。

我们可以让 Gnus 把 `Content-Type' 和 `Content-Transfer-Encoding' 字段显示出来:

(add-hook 'gnus-startup-hook
'(lambda ()
(setq gnus-visible-headers "^\\(^To:\\|^CC:\\|^From:\\|^Subject:\\|^Date:\\|^Followup-To:\\|^X-Newsreader:\\|^User-Agent:\\|^X-Mailer:\\|Line:\\|Lines:\\|Content-Type:\\|NNTP-Posting-Host\\)")

这样我们就可以看到邮件采用了什么编码(顺便还可以看到对方用的是什么客户端软件),如果邮件指定了编码,你就会在邮件头部看到这一行:

Content-Type: text/plain; charset=GB18030

如果邮件指定了传输编码,就可以看到这样一行:

Content-Transfer-Encoding: base64

** Gnus 中与编码有关的变量

需要说明的是,大多数情况下,邮件都是比较文明的,在 Content-Type 字段已经说明了邮件采用的编码,那么 Gnus 将自动按指定的编码显示邮件。除非 Emacs 本身就不支持这种编码,否则是不会出现乱码的。

一个常见情况就是使用 Emacs21 或 Emacs22,而不安装 mule-gbk,要知道Emacs21/22 本身就不支持 GBK 和GB18030,这样对于 charset=gbk, 或charset=gb18030 的邮件出现乱码就不奇怪了。所以,如果你使用的是 Emacs21 或Emacs22, 赶紧去下载 mule-gbk 装上吧。注意,对于 Emacs22,mule-gbk 的设置中一定要加上这一句:

(utf-translate-cjk-load-tables)

否则 Emacs22 无法进行 gbk <--> utf-8 的转换,而 gnus 是先用 utf-8 编写邮件,再转换成指定的编码发出去,如果不加上这句设置,就会出现只能发 utf-8的邮件,不能发 gbk 邮件的怪现象。

下面讨论的设置适用于 Emacs22+mule-gbk 和 Emacs23。

如果邮件中已经用 Content-Type 和 Content-Transfer-Encoding 指定了编码,那么 gnus 将忠实地采用这些编码处理邮件,这时是不应该出现乱码的。所以,我们所做的设置基本上都是为了对付那些捣乱的,不指明编码的邮件。

现在我们来看看 gnus 中与编码设置有关的变量。这些变量可以分为两种类型,一种是与邮件内容有关的,另一种是与组名(group name)有关的。

** 与邮件内容有关的变量

*** gnus-default-charset

这个变量指定查看邮件所用编码的默认值(对于未指定编码的邮件)。但是这个变量的会被下一个变量(gnus-group-charset-alist)覆盖。如果不设置这个变量,它的值将由 `current-language-environment' 确定。

例如:(setq gnus-default-charset 'gbk)

*** gnus-group-charset-alist

这个变量根据组名确定本组的默认编码。设置这个变量时我们要给出一个(regexp charset) 对,其中 regexp 时匹配组名的正则表达式,charset 是指定的编码。在这个变量的默认设置中,我们可以查到如下内容:

("\\(^\\|:\\)hk\\>\\|\\(^\\|:\\)tw\\>\\|\\" cn-big5)
("\\(^\\|:\\)cn\\>\\|\\" cn-gb-2312)

可以看出,对于以 hk 或 tw 开头(或者组名中包含 :hk 或 :tw)的组,采用big5编码。而对于以 cn 开头或组名中包含 :cn 的组采用 cn-gb-2312 的编码。

因为现在 gbk 比 gb2312 应用更广泛,所以我们需要更改这个变量的设置:

(add-to-list 'gnus-group-charset-alist '("\\(^\\|:\\)cn\\>\\|\\" gbk))

这个变量也可以在 group parameter 中以 (charset . gbk) 的方式指定。

*** gnus-summary-show-article-charset-alist

有时候,默认的编码还不能解决问题,例如,有人把 big5 编码的邮件投递到了 cn 开头的组里,而且邮件头中又没有编码设定(插一句,这些邮件一般都是垃圾邮件),这时就需要手工指定编码。

(setq gnus-summary-show-article-charset-alist '((1 . utf-8) (2 . big5) (3 . gbk) (4 . utf-7)))

进行了这种设定以后,我们看到乱码邮件时就可以用 `1 g' 指定采用 utf-8,`2 g' 指定big5等等,不过能不能正确解码就要看你自己猜的对不对了。

** 与组名有关的变量

我们订阅新闻组时,可以看到有些服务器上的组名都是英文的,比如在`news.cn99.com' 这个服务器中组名都是这样的:

gnu.emacs.help
cn.comp.os.linux
tw.bbs.os.linux

而有些服务器的组名却是包含中文的,比如新帆`news.newsfan.net'的组名:

计算机.软件.操作系统.FreeBSD
计算机.软件.操作系统.linux
休闲娱乐.游戏天地.Diablo

下面两个变量是为了让gnus能正确地处理非ascii组名的。

*** gnus-group-name-charset-group-alist

这个变量根据组名确定组名采用的编码,默认值是`((".*" utf-8))',也就是默认用 utf-8 处理所有组名。我们可以这样这样设置:

(setq gnus-group-name-charset-group-alist '(("\\.com\\.cn:" . gbk) ("news\\.newsfan\\.net" . gbk)))

这样所有组名中含有 .com.cn 或 news.newsfan.net 的组,其名称都采用 gbk解码。

不过如果我们把 news.newsfan.net 设置为 native method, 那么组名中就不会出现 news.newsfan.net,那么这个变量就发挥不了作用,怎么办呢?可以采用下面这个变量。

*** gnus-group-name-charset-method-alist

还记得吗?我们选择新闻服务器时是怎么设定的?对了,我们是通过设置method 来选择服务器的,比如:

(setq gnus-select-method '(nntp "news.newsfan.net"))
(setq gnus-secondary-select-methods '((nnml "")))

这个变量可以根据我们选择的 method,为来自这个 method 的组设置组名的编码。

(setq gnus-group-name-charset-method-alist '(((nntp "news.newsfan.net") . gbk)))

这样,所有来自新帆服务器的组名都采用gbk来解码。

** 设置发出邮件的编码: mm-coding-system-priorities

当我们向外发送邮件时,也可以指定编码,比如我们希望发出的邮件采用gb2312编码,就可以这样设置:

(setq mm-coding-system-priorities '(iso-8859-1 gb2312 utf-8))

这样,gnus将先试这采用 iso-8859-1 编码邮件,如果不行就采用 gb2312,实在不行再采用 utf-8 编码。

这样如果你写了一封纯英文的信件,将会采用 iso-8859-1 发出;如果你写了一封中文信件,但其中的汉字都在 gb2312 的范围内,则采用gb2312发出;如果你的信件中含有gb2312以外的字符,则会被以utf-8编码发出。

那么如果想对不同的组采用不同的编码发信,有办法实现吗?可以,通过设置posting-style 就可以实现。

(setq gnus-posting-styles '((".*"
(name "xxx")
(address "xxx@xxx.xxx")
(eval (setq mm-coding-system-priorities '(iso-8859-1 utf-8))))
("^cn\\.comp";
(name "xxx")
(address "xxx@xxx.xxx")
(eval (setq mm-coding-system-priorities '(iso-8859-1 gb2312 utf-8))))
("^tw\\.comp"
(name "xxx")
(address "xxx@xxx.xxx")
(eval (setq mm-coding-system-priorities '(iso-8859-1 big5 utf-8))))))

这样对于 cn.comp 开头的组,gnus会先尝试采用gb2312发送邮件,不行再用utf-8,而对于 tw.comp 开头的组,会先尝试采用big5发送邮件,不行再用utf-8。

** 处理有问题的邮件: gnus-newsgroup-ignored-charsets

有些客户端发出的邮件没有指定正确的MIME类型,例如本来这封邮件是用 gbk 编码的,但是 MIME 类型却设置成了 x-gbk:

Content-Type: text/plain; charset=x-gbk

这时gnus解码时会遇到困难,我们可以把这种 MIME 类型加入到gnus-newsgroup-ignored-charsets 列表中,让 gnus 采用默认的编码处理它。

再比如,有些邮件的 MIME 类型是 charset=gb18030, 对于 emacs23,这是没问题的,因为 Emacs23 支持 gb18030 编码。但是 emacs22+mule-gbk 根本就不支持gb18030,那么该怎么办呢?同样我们可以把 gb18030 加入gnus-newsgroup-ignored-charsets 列表中:

(setq gnus-newsgroup-ignored-charsets '(unknown-8bit x-unknown x-gbk gb18030))

ignored-charsets 也可以在 group parameters 中这样指定:

(ignored-charsets x-unknown iso-8859-1)

** 指定传输编码:gnus-group-posting-charset-alist

这个变量可以用来设置邮件头中的 Content-Transfer-Encoding 字段,为邮件指定传输编码。这个变量的默认值已经设置的很好了,我从来没有遇到需要设置这个变量的情况。

** 不要让自己发出乱码的邮件(指定附件文件名和subject的编码方式)

gnus 默认采用 RFC2231 对附件文件名进行编码,有些 MUA 无法识别这种编码。现在比较流行的方式是采用 RFC2047 对附件文件名进行编码。可以采用如下设定,让gnus 也采用这种方式对文件名进行编码:

(defalias 'mail-header-encode-parameter 'rfc2047-encode-parameter)

有很多差劲的邮件客户端无法解码 quoted-printable 编码(看到过 subject 中有很多 `=' 号的乱码邮件吗?就是由于这个原因产生的。)为了保证我们发出的邮件subject 采用 base64 编码,而不是采用quoted-printable 编码,最好加上这两句:

(add-to-list 'rfc2047-charset-encoding-alist '(gbk . B))
(add-to-list 'rfc2047-charset-encoding-alist '(gb18030 . B))

** 参考设置

最后给出一个参考设置吧,适用于 Emacs22+mule-gbk 或者 Emacs23:

(setq gnus-default-charset 'gbk)
(add-to-list 'gnus-group-charset-alist '("\\(^\\|:\\)cn\\>\\|\\" gbk))
(setq gnus-summary-show-article-charset-alist '((1 . utf-8) (2 . big5) (3 . gbk) (4 . utf-7)))
(setq gnus-group-name-charset-group-alist '(("\\.com\\.cn:" . gbk) ("news\\.newsfan\\.net" . gbk)))
(setq gnus-group-name-charset-method-alist '(((nntp "news.newsfan.net") . gbk)))
(setq gnus-newsgroup-ignored-charsets '(unknown-8bit x-unknown x-gbk gb18030))

(defalias 'mail-header-encode-parameter 'rfc2047-encode-parameter)
(add-to-list 'rfc2047-charset-encoding-alist '(gbk . B))
(add-to-list 'rfc2047-charset-encoding-alist '(gb18030 . B))

(setq gnus-posting-styles '((".*"
(name "xxx")
(address "xxx@xxx.xxx")
(eval (setq mm-coding-system-priorities '(iso-8859-1 utf-8))))
("^cn\\.comp";
(name "xxx")
(address "xxx@xxx.xxx")
(eval (setq mm-coding-system-priorities '(iso-8859-1 gb2312 utf-8))))
("^tw\\.comp"
(name "xxx")
(address "xxx@xxx.xxx")
(eval (setq mm-coding-system-priorities '(iso-8859-1 big5 utf-8))))))

(add-hook 'gnus-startup-hook
'(lambda ()
(setq gnus-visible-headers "^\\(^To:\\|^CC:\\|^From:\\|^Subject:\\|^Date:\\|^Followup-To:\\|^X-Newsreader:\\|^User-Agent:\\|^X-Mailer:\\|Line:\\|Lines:\\|Content-Type:\\|NNTP-Posting-Host\\)")

因为这篇文章主要是讨论Gnus的编码问题,所以这里列出的仅仅是与编码有关的设置。

没有评论: