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

使用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已经加了说明文字。

Delphi2010 实现邮件附件收发功能 TIdPOP3组件简介 TIdPOP3 是用来接收邮件服务器的邮件信息到用户端的一个组件。它实现了RFC 1939协议。 在使用TIdPOP3组件时需设置它的几个成员属性。 Host :指定邮件服务器,一般为pop3邮件服务器的地址,如 pop3.126.com。 Username :用户名,也就是邮箱名,如billanking2002@126.com。 Password :邮箱密码,在进行收发邮件时组件需要凭密码进行登录。 其它成员属性 Connected:返回它与邮件服务器的连接状态,这true表示已经连接。 CheckMessages:邮件数,如果连接服务器成功,则可以获得服务器端的邮件数。 成员函数 procedure Connect(const ATimeout: Integer = IdTimeoutDefault); 与服务器连接函数。参数为无效连接时等待的毫秒数。 function RetrieveHeader(const MsgNum: Integer; AMsg: TIdMessage): Boolean; 接收邮件头信息,它有两个参数,MsgNum表示在接收第几个邮件,从1开始,AMsg为邮件消息组件实例。 function Retrieve(const MsgNum: Integer; AMsg: TIdMessage): Boolean; 接收邮件主体信息,它与 RetrieveHeader的参数是一样的。接收邮件内容将保存在AMsg中。 function Delete(const MsgNum: Integer): Boolean; 删除邮件服务器中第几个邮件。从1开始。 procedure Disconnect; override; 关闭连接。 TIdMessage组件简介 TIdMessage用来支持邮件消息协议,如POP3,SMTP,NNTP等。TIdMessage支持多用途Internet邮件扩展(MIME)协议。 常用的TIdMessage的属性: Subject:邮件主题,这个字符串经过BASE64编码的。所以在使用时需对它进行解码。 MessageParts:这是TIdMessageParts类的一个实例,它用来存储邮件的信息。如邮件内容及附件信息。在进行解析时需要判断它是否为附件或文本,如果为附件时,其文件名是经过BASE64编码的。判断常量分别为TIdText , TIdAttachment。 Body:这是个字符串列表,包含构成该邮件的正文内容。 Form:发送邮件者的地址信息。 Recipients:收件人地址信息。 BccList:抄送地址列表。 CharSet:指示邮件信息中使用的字符集。 ContentType:指定MIME媒体数据类型,描述正文中包含的数据,使用户代理决定如何显示数据,常用的有text/html,text/xml。 TIdSMTP组件简介 TIdSMTP是TIdMessageClient派生出的一个简单邮件传输协议和SMTP客户端。 它的主要功能是发送邮件信息。 常用的属性: Host :SMTP邮件服务器的地址,如smtp.126.com。它与上面的POP3地址不一样。 AuthenticationType:服务器认证类型,它有atNone,atLogin两种,即不需要认证和需要凭用户名和密码进行认证。 Username:用户名,这里与TIdPOP3 有点不一样,即它不需要后缀,如billanking2002 Password:邮箱登录密码。如果AuthenticationType设置了atLongin则必须设置密码和用户名。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值