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

Delphi 专栏收录该内容
150 篇文章 2 订阅

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

  • 0
    点赞
  • 0
    评论
  • 0
    收藏
  • 扫一扫,分享海报

参与评论 您还未登录,请先 登录 后发表或查看评论
©️2022 CSDN 皮肤主题:大白 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值