首页 新闻 论坛 群组 Blog 文档 下载 读书 Tag 网摘 搜索 开源 FAQ 第二书店 博文视点 程序员
频道: 研发 数据库 中间件 信息化 视频 .NET Java 游戏 移动 服务: 人才 外包 培训
    图书品种:235680
       
热门搜索: ASP.NET Ajax Spring Hibernate Java

在电子邮件中使用UTF-8

Using UTF-8 with Email

如果应用程序需要发送电子邮件,那邮件本身就需要支持应用程序使用的字符集和编码。否则,您可能遇到这种情况,用户可以使用西里尔名称注册,但你不能通过任何电子邮件发送问候给他。

在即将发送的电子邮件中指明具体字符集和编码,和在网页中指明字符集和编码很类似。每一封电子邮件都有一个或多个的标头段,形式上类似HTTP 标头,可描述各种事情,比如邮件接受者、时间、标题等。字符集和编码方式可通过content-type 标头来制定,就和HTTP响应一样:

Content-Type: text/plain; charset=utf-8

content-type标头的问题在于它描述的是电子邮件主体的内容。和HTTP一样,邮件标头也必须是纯粹的ASCII。而许多邮件传输代理都不是8-bit安全的,会丢弃ASCII范围之外的字符。如果想要把任何数据放置在标头部分,如标题或发送者名称,那么我们需要使用ASCII编码。

如果你有可爱的UTF-8数据,而且你希望能把它用在邮件标题行,那么这简直要让人发疯。但很幸运,有一个比较简单的解决办法。标头可以包括一些在RFC 1342(“非ASCII文本在互联网消息标头上的表示”)上的定义作为编码字。一个编码字看起来是这样的:

=?utf-8?Q?hello_=E2=98=BA?=

=?charset?encoding?encoded-text?=

charset 元素包含了字符集名称,以及编码是“B”还是“Q”。被编码的文本使用了指定字符集的字符串,并且使用了已制定的方法进行编码。

“B”编码就是直接的base64编码,它定义在RFC 3548中。“Q.”编码是quoted-printable编码的一个变种,它有以下规则:

l          任何字节都可以表示为字面上的等于号(=)加上两个十六进制字符。比如,字节0x8A可以表示为=8A

l          空格(字节 0x20)必须使用字面上的下划线(_,字节0x5F)来替代

l          ASCII的文字和数字字符保持原状不变

通常更为推荐这种quoted printable“Q”方法,因为简单的ASCII字符串还是能够被辨认出来。这能给调试提供很大的帮助,并且在ASCII终端让你能够容易地读取邮件的原始标头,并且大致理解它们。

这个编码能够通过一个小的PHP函数实现:

function email_escape($text){

       $text = preg_replace('/([^a-z ])/ie', 'sprintf("=%02x", ord(StripSlashes("

       \\1")))', $text);$text = str_replace(' ', '_', $text);

       return "=?utf-8?Q?$text?=";

}

我们还可以给它一个小小的改进,只转译基本字符之外的字符串。这样可以为每份发送出去的邮件节省一些字节,并且能让源代码更加可读。

function email_escape($text){

      if (preg_match('/[^a-z ]/i', $text)){

          $text = preg_replace('/([^a-z ])/ie', 'sprintf("=%02x",

          ord(StripSlashes("\\1")))', $text);$text = str_replace(' ', '_', $text);

          return "=?utf-8?Q?$text?=";

}

return $text;

}

RFC 1342称任何单独的编码其长度最长不得超过75个字符,为了使函数完全兼容,我们需要作一些补充修改。因为我们知道每个编码都需要12个额外的无价值的字符(你可以数一数它们),所以可以将编码文本切分成63个或更少字符的段落,给每段添加前缀和后缀,并且在每段之间添加换行符。当然,我们得小心,不要把一个编码字符拦腰截断了。完整的函数实现就留给读者作为一个练习。

我们已经讨论了主体和标题的编码,剩下的就是把我们所学的内容打包在一起,成为一个单独的能够安全发送UTF-8邮件的函数:

function email_send($to_name, $to_email, $subject, $message, $from_name,

$from_email){

     $from_name = email_escape($from_name);

     $to_name   = email_escape($to_name);

     $headers  = "To: \"$to_name\" <$to_email>\r\n";

     $headers .= "From: \"$from_name\" <$from_email>\r\n";

     $headers .= "Reply-To: $from_email\r\n";

     $headers .= "Content-Type: text/plain; charset=utf-8";

     $subject = email_escape($subject);

     mail($to_email, $subject, $message, $headers);

}

JavaScript中使用UTF-8

Using UTF-8 with JavaScript

现代浏览器都支持内置于JavaScript语言的Unicode,基本的String类存放的是码点而不是字节,并且可以使字符串操作函数正确工作。当你使用JavaScript拷贝数据输入和输出于表单时,这些数据最终以UTF-8格式提交(假定你指定它作为页面的编码类型)。

惟一要注意的是内建的函数escape(),它可用来格式化包含在URL中的字符串,而且并不支持Unicode字符。 这意味着如果想让用户输入文本,并通过此输入来构建一个URL(比如构建一个GET查询字符串),那你就不能使用escape()。幸好,Javascript代码生来支持码点,还提供了String.getCodeAt()方法来查询它们,因此你可以很容易地写出自己的UTF-8的安全转义函数:

function escape_utf8(data) {

        if (data == '' || data == null){

               return '';

        }

       data = data.toString( );

       var buffer = '';

       for(var i=0; i<data.length; i++){

               var c = data.charCodeAt(i);

               var bs = new Array( );

               if (c > 0x10000){

                       // 4 bytes

                       bs[0] = 0xF0 | ((c & 0x1C0000) >>> 18);

                       bs[1] = 0x80 | ((c & 0x3F000) >>> 12);

                       bs[2] = 0x80 | ((c & 0xFC0) >>> 6);

                   bs[3] = 0x80 | (c & 0x3F);

               }else if (c > 0x800){

                       // 3 bytes

                       bs[0] = 0xE0 | ((c & 0xF000) >>> 12);

                       bs[1] = 0x80 | ((c & 0xFC0) >>> 6);

                       bs[2] = 0x80 | (c & 0x3F);

               }else if (c > 0x80){

                       // 2 bytes

                       bs[0] = 0xC0 | ((c & 0x7C0) >>> 6);

                   bs[1] = 0x80 | (c & 0x3F);

               }else{

                       // 1 byte

                   bs[0] = c;

               }

               for(var j=0; j<bs.length; j++){

                         var b = bs[j];

                         var hex = nibble_to_hex((b & 0xF0) >>> 4)

                         + nibble_to_hex(b &0x0F);buffer += '%'+hex;

               }

    }

    return buffer;

}

function nibble_to_hex(nibble){

        var chars = '0123456789ABCDEF';

        return chars.charAt(nibble);

}

该escape_utf8()函数依次迭代字符串中的每个码点,建立一个UTF-8字节流。然后遍历流中的每个字节,使用%XX格式化每个字节,从而转义URL中的字节。对这个函数的进一步改进,可以是在字符串的转义版本中保留字母和数字字符的原状不变,从而可以使返回值在通常情况下比较易读。

API中使用UTF-8

Using UTF-8 with APIs

API 有两个向量需要设定字符集和编码:输入和输出。(整本书中,API 这个名词都是指外部Web服务API,除非另有说明。我们谈的不是语言工具或类的API。)

关于输出, 你可能已经知道该怎么做了。如果API回应是基于XML的,那么你可以用我们先前讨论过的同样的HTTP和XML标头。如果你的输出是基于HTML的,HTTP标头和<meta>标签的组合就能够很好地工作。

对于其他定制的输出,如果你有一些办法来确定流的开端,使用BOM可能是一个好主意。如果你不能或不想用BOM,那么没有什么是比文档化你所发送的内容更重要的。早期就明确你的输出字符集和编码,能够防止人们开发出一开始还能工作但是一遇到外来的文本就崩溃的应用程序。

API 的输入可能是一个更大的问题。正如俗语所说,惟一不比电脑更聪明的就是它们的用户。如果你暴露了应用程序的公开的API,那你将不能保证所有发送的文本都有正确的字符集。对于输入向量,极其重要的是要确认所有的输入是有效的并且完好的——这是在下一章我们要详细探讨的内容。

查看所有评论(0)条】

最近评论



正在载入评论列表...
热点评论