Quoted-Printable编码原理及代码实现

来源:互联网 发布:php curl_exec 返回值 编辑:程序博客网 时间:2024/05/17 09:00

这篇文章是我之前在RYTong内部分享的一篇文章,摘取了有用的部分。当时帮助某项目邮件系统解决问题,期间了解到Quoted-Printable编码,在此与大家分享下该编码的原理和个人版本的代码实现。

关于规范

关于Quoted-Printable的编码规范,需要参考rfc2045。

为了方便大家阅读,在此给大家看一下融合我个人理解的翻译:

  1. 除了换行(CRLF序列)中的CR或LF,所有(8bit)字节都可以表示为符号”=”后跟两个16进制数字(16进制数字表示该字节的值)的格式,16进制数字只能有数字和大写ABCDEF表示,不能用小写字母。例如,ASCII码换页符,数值为12,编码后表示为”=0C”。

  2. 值为33到126的字节(除61外),包含边界,也可以不进行编码。

  3. 制表符(值为9)和空格(值为32)可以不进行编码,但未编码的两者不能位于编码后的行尾。如果行尾由软换行符(符号”=”,参照规则5)时,软换行符前的制表符和空格可以不编码。两者若在行尾则必须编码。

  4. 换行(CRLF序列)不必进行编码。因为只有CRLF序列表示换行,因此包含CR或LF的其他序列须进行编码。

  5. Quoted-Printable编码要求编码后的文本每行长度不得超过76字符。如果原文的一行被编码后的文本长度超过76,那么需要在编码后的行尾添加软换行符”=”,表示原文此处没有换行。注意软换行符”=”也会计入编码后的字节长度,也就是说如果编码后行尾存在软换行符,那么该行其他字符数不得超过75。

  6. 建议对-!\”#$@[\]^`{|}~进行编码,以避免对其他协议或少数网关(如EBCDIC)的处理造成影响。

  7. 建议对CRLF序列进行编码,避免各平台不同的换行序列对解码造成影响。

关于代码实现

以下是本人基于以上规则实现的代码(Erlang实现)

-module(qp_encoder).-export([qp_encode/1]).-define(LINE_LENGTH, 76).-define(is_suggested(C),        C == 45 orelse C == 64 orelse C == 96 orelse C == 46 orelse            (C >= 33 andalso C =< 36) orelse            (C >= 91 andalso C =< 94) orelse            (C >= 123 andalso C =< 126)).-define(CR, 13).-define(LF, 10).-define(HT, 9).-define(SPACE, 32).%%%%%%%%%%%%%%%%%%%%%%%%%% API %%%%%%%%%%%%%%%%%%%%%%%%%%%% @spec (Input :: binary() | string()) -> Output :: binary()qp_encode(String) when is_list(String) ->    qp_encode(list_to_binary(String));qp_encode(Bin) when is_binary(Bin) ->    qp_encode(0, Bin, <<>>, <<>>, <<>>).%% @spec (Input :: binary() | string()) -> Output :: binary()qp_decode(String) when is_list(String) ->    qp_decode(list_to_binary(String));qp_decode(Bin) when is_binary(Bin) ->    qp_decode(Bin, <<>>, 1, 1).%%%%%%%%%%%%%%%%%%%%%%%%%% internal %%%%%%%%%%%%%%%%%%%%%%%%%%%% Num is the total number of characters of LineAcc + LastBuf%% Bin is the input binary to be encoded%% LastBuf is the encoded binary of last character%% LineAcc is the current line of encoded binary%% Acc is the output binaryqp_encode(Num, Bin, LastBuf, LineAcc, Acc) when Num >= ?LINE_LENGTH ->    {CurrentLine, LastBuf2, NextLineNum} = qp_new_line(LastBuf, LineAcc),    qp_encode(NextLineNum, Bin, LastBuf2, <<>>,              <<Acc/binary, CurrentLine/binary>>);qp_encode(_, <<>>, LastBuf, LineAcc, Acc) ->    Encoded =        case LastBuf of            <<C>> when C == ?HT orelse C == ?SPACE ->                do_qp_encode(C);            _ ->                LastBuf        end,    <<Acc/binary, LineAcc/binary, Encoded/binary>>;qp_encode(Num, <<C, Rest/binary>>, LastBuf, LineAcc, Acc) ->    {EncNum, Enc} = inline_qp_encode(C),    qp_encode(Num + EncNum, Rest, Enc,              <<LineAcc/binary, LastBuf/binary>>, Acc).%% nethier HT nor SPACE could be presented at the end of an encoded lineqp_new_line(<<C>>, CurrentLine) ->    {<<CurrentLine/binary, $=, ?CR, ?LF>>, <<C>>, 1};qp_new_line(LastBuf, CurrentLine) ->    {<<CurrentLine/binary, $=, ?CR, ?LF>>, LastBuf, 3}.%% characters(-!\"#$@[\]^`{|}~.) are suggested to be encodedinline_qp_encode(C) when ?is_suggested(C) ->    {3, do_qp_encode(C)};%% character "=" must be encodedinline_qp_encode($=) ->    {3, do_qp_encode($=)};%% character whose ascii code is between 33 and 126 inclusively need not%% be encodedinline_qp_encode(C) when C >= 33 andalso C =< 126 ->    {1, <<C>>};%% HT and SPACE need not be encodedinline_qp_encode(C) when C == ?HT orelse C == ?SPACE ->    {1, <<C>>};%% others must be encodedinline_qp_encode(C) ->    {3, do_qp_encode(C)}.do_qp_encode(Int) when is_integer(Int) ->    [Hex1, Hex2] =        case integer_to_list(Int, 16) of            [H] ->                [$0, H];            _Val ->                _Val        end,    <<$=, Hex1, Hex2>>.

第一条规则由do_qp_encode/1函数实现,这部分值得注意的是integer_to_list/2方法的使用,可以直接将一个整数转成对应的16进制字符串。
第二条规则由67到70行的代码实现,此处用了Erlang模式匹配的技巧,由于会优先匹配68行的函数,因此72行函数判断条件并不需要将“=”号排除。
第三条规则由75和76行的代码实现,大家可能会觉得此处逻辑存在问题,因为编码后行尾是不能存在未编码的制表符(值为9)和空格(值为32)的。这里是因为我按规则7将CRLF进行了编码,因此编码后的每行都是以“=”结束的软换行,所以我并未对制表符和空格进行编码。由于最后一行的编码内容是不存在软换行的,因此我加了46到51行的代码处理,将最后一行末尾出现的制表符和空格进行了编码。
第四条规则和第七条有冲突,这里我按第7条进行了实现。
第五条规则由40到43行代码实现。
第六条规则由65和66行代码实现。大家可以注意到,我在代码逻辑里增加了对“.”号的编码,这是由于我们发现在实际使用(邮件转发)中,发现部分出现在行首的“.”号会被其他服务器删除掉(原因未知),因此特意对该符号进行了编码,编码之后此问题解决。

除了Quoted-Printable编码规则外,大家还可以通过这个例子了解一下Erlang binary的相关语法,用起来还是很方便的。有兴趣的同学可以先学习一下《Programming Erlang》这本书的5.2和5.3章节。

0 0
原创粉丝点击