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

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

2016-05-27 21:20 721 查看
使用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已经加了说明文字。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  乱码 idsmtp