您的位置:首页 > 编程语言 > Delphi

彻底解决delphi Indy10接收邮件汉字显示乱码的问题

2011-01-27 11:01 537 查看
使用indy组件接收邮件时,遇到汉字大多显示为乱码,网上很多询问同类型的问题。这几天做一个邮件客户端的小项目,研究了一下Indy10的代码,发现有办法根本解决这个问题,感觉牵涉的知识点挺多的,在这里讲解一下,给需要解决此问题的朋友参考。

1。 计算机系统中码字表示方法

我们经常熟悉的码字表示有多种方式:ASCII, Unicode, UTF8, UTF16,...,汉字的包括GB2312, GBK, UTF8, BIG5, Unicode等。

程序开发人员要明确的是网络传输的是字节流,和码字没关系。

2。Indy对邮件码字的解析

Indy接收到字节流后会按照邮件传输的协议(应该是RTF 822吧,记不清楚了)解析字节流。当发现需要按照特定的码字解释字节流时,使用TIdTextEncoding进行编码。这个TIdTextEncoding使用协议中CharSet对应的CodePage进行创建, Indy内部有一张CodePage表,CodePage表说白了就是字节流中每一个字节或几个字节应该翻译成什么字符。如果你的操作系统支持这种CodePage的话,在Indy内部应该能看到可读的文字了,也许是汉字,也许是英文字符。

3。问题出现

在经过上一步后,Indy并不知道当前系统使用什么样的CodePage,也没有从系统中查找当前使用的CodePage,在转换到目的编码(本机系统编码)时使用了ADestEncoding = nil 作为目标编码,缺省使用ASCII,自然问题就来了,显示出来一堆一堆的'?',这个时候再用别的方式转换也就没用了,比如WideCharToMultiBytes, MultiBytesToWideChar统统失效。

4。解决问题

经过跟踪Retrieve函数(我只处理了Body部分,其他部分应该可以同样处理),发现在处理Text的时候,有一个函数ReadStringsAsContentType,声明如下:

procedure ReadStringsAsContentType(AStream: TStream; AStrings: TStrings;
const AContentType: String; AQuoteType: TIdHeaderQuotingType
{$IFDEF STRING_IS_ANSI}; ADestEncoding: TIdTextEncoding = nil{$ENDIF}

其中:AStream是网络流;AStrings是文本保存变量;AContentType是邮件内容说明,包含ContentType和CharSet;AQuoteType是MIME, ADestEncoding是目标编码。

对应上面讲解的知识,可以发现AContentType决定了网络流中的编码,或则说是发信方指定的编码。ADestEncoding是接收方指定编码。Indy把这个ADestEncoding缺省设定为nil了。

对于Body得部分,ProcessTextPart中调用了ReadStringsAsContentType,修改如下(黑色部分):

{Only set AUseBodyAsTarget to True if you want the input stream stored in TIdMessage.Body
instead of TIdText.Body: this happens with some single-part messages.}
procedure ProcessTextPart(var VDecoder: TIdMessageDecoder; AUseBodyAsTarget: Boolean);
var
LMStream: TMemoryStream;
i, cp: integer;
LTxt : TIdText;
LHdrs: TStrings;
LNewDecoder: TIdMessageDecoder;
{$IFDEF STRING_IS_ANSI}
LDestEncoding: TIdTextEncoding;
{$ENDIF}
begin
LMStream := TMemoryStream.Create;
try
LParentPart := AMsg.MIMEBoundary.ParentPart;
LNewDecoder := VDecoder.ReadBody(LMStream, LMsgEnd);
try
LMStream.Position := 0;
if AUseBodyAsTarget then begin
if AMsg.IsMsgSinglePartMime then begin
ReadStringsAsCharSet(LMStream, AMsg.Body, AMsg.CharSet);
end else begin
ReadStringsAsContentType(LMStream, AMsg.Body, VDecoder.Headers.Values[SContentType], QuoteMIME);
end;
end else begin
if AMsg.IsMsgSinglePartMime then begin
LHdrs := AMsg.Headers;
end else begin
LHdrs := VDecoder.Headers;
end;
LTxt := TIdText.Create(AMsg.MessageParts);
try
{$IFDEF STRING_IS_ANSI}
cp := GetOEMCP;
LDestEncoding := TIdTextEncoding.GetEncoding(cp);
ReadStringsAsContentType(LMStream, LTxt.Body, LHdrs.Values[SContentType], QuoteMIME, LDestEncoding);
{$ELSE}
ReadStringsAsContentType(LMStream, LTxt.Body, LHdrs.Values[SContentType], QuoteMIME);
{$ENDIF}
RemoveLastBlankLine(LTxt.Body);
LTxt.ContentType := LTxt.ResolveContentType(LHdrs.Values[SContentType]);
LTxt.CharSet := LTxt.GetCharSet(LHdrs.Values[SContentType]); {do not localize}
LTxt.ContentTransfer := LHdrs.Values[SContentTransferEncoding]; {do not localize}
LTxt.ContentID := LHdrs.Values['Content-ID']; {do not localize}
LTxt.ContentLocation := LHdrs.Values['Content-Location']; {do not localize}
LTxt.ContentDescription := LHdrs.Values['Content-Description']; {do not localize}
LTxt.ContentDisposition := LHdrs.Values['Content-Disposition']; {do not localize}
if not AMsg.IsMsgSinglePartMime then begin
LTxt.ExtraHeaders.NameValueSeparator := '='; {do not localize}
for i := 0 to LHdrs.Count-1 do begin
if LTxt.Headers.IndexOfName(LHdrs.Names[i]) < 0 then begin
LTxt.ExtraHeaders.Add(LHdrs.Strings[i]);
end;
end;
end;
LTxt.Filename := VDecoder.Filename;
if IsHeaderMediaType(LTxt.ContentType, 'multipart') then begin {do not localize}
LTxt.ParentPart := LPreviousParentPart;

// RLebeau 08/17/09 - According to RFC 2045 Section 6.4:
// "If an entity is of type "multipart" the Content-Transfer-Encoding is not
// permitted to have any value other than "7bit", "8bit" or "binary"."
//
// However, came across one message where the "Content-Type" was set to
// "multipart/related" and the "Content-Transfer-Encoding" was set to
// "quoted-printable". Outlook and Thunderbird were apparently able to parse
// the message correctly, but Indy was not. So let's check for that scenario
// and ignore illegal "Content-Transfer-Encoding" values if present...

if LTxt.ContentTransfer <> '' then begin
if PosInStrArray(LTxt.ContentTransfer, ['7bit', '8bit', 'binary'], False) = -1 then begin {do not localize}
LTxt.ContentTransfer := '';
end;
end;
end else begin
LTxt.ParentPart := LParentPart;
end;
except
LTxt.Free;
raise;
end;
end;
except
LNewDecoder.Free;
raise;
end;
VDecoder.Free;
VDecoder := LNewDecoder;
finally
FreeAndNil(LMStream);
end;
end;

OK,运行一下看看。也许还有其他要修改,编程的朋友可以一起找找。

如果把Indy的代码仔细看看,会发现有很多可能会需要定制的地方,Indy已经加了说明文字。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: