Delphi Indy SMTPServer读取多部分消息

时间:2018-02-02 09:19:58

标签: delphi indy indy10 delphi-10.2-tokyo

我正在使用Delphi 10.2 Tokyo和Indy 10.6.2.5366。

我正在尝试从外部系统接收一些SMTP消息,并且收到的消息使用多部分MIME。我需要从消息中提取一些字段和值。

当我在Indy SMTPServer Demo中读取正文时,正文为空,但正文被写入保存的文件。

如何将该身体读入TStringList?这是我必须开发的手册,还是有一些方法(找不到一个)来做这个?

这就是我的所作所为(灵感来自演示):

procedure TForm1.IdSMTPServer1MsgReceive(ASender: TIdSMTPServerContext;
  AMsg: TStream; var LAction: TIdDataReply);
var
 LMsg : TIdMessage;
 LMsg2 : TIdMessage;
 LStream : TFileStream;
 i : integer;
 AMsgpart : TidMessagePart;

begin
// When a message is received by the server, this event fires.
// The message data is made available in the AMsg : TStream.
// In this example, we will save it to a temporary file, and the load it using
// IdMessage and parse some header elements.

  AddToLog('Msg recv '+formatdatetime('hh:nn:ss', Now));

  LMsg2 := TIdMessage.Create;
  try
    LMsg2.LoadFromStream(AMsg);
    ToLabel.Caption := LMsg2.Recipients.EMailAddresses;
    FromLabel.Caption := LMsg2.From.Text;
    SubjectLabel.Caption := LMsg2.Subject;

// not working
//    Memo1.Lines := LMsg2.Body;

// so what to do with this multi-part message in MIME format.
    for i := 0 to LMsg2.MessageParts.Count-1 do
    begin
      if (LMsg2.MessageParts.Items[i].PartType = mptText) then
       begin
         //AddToLog(LMsg2.MessageParts.Items[i].);
         AMsgpart := LMsg2.MessageParts.Items[i];




       end
    end;
  finally
    FreeAndNil(LMsg2);
  end;

  // Just write to a file as in demo
  LStream := TFileStream.Create(ExtractFilePath(Application.exename) + format('test(%d).eml',[mailno]), fmCreate);
  Try
    LStream.CopyFrom(AMsg, 0);
  Finally
    FreeAndNil(LStream);
  End;

end;

写入文件时的消息流如下所示

Received: from WIN-2SP97MPF39L[192.168.10.141] (helo=DesigoCC1) by HPNB2.hnit.local[192.168.10.131] with esmtp (My SMTP Server)
From: Alarms@DCC.dk
Date: Fri, 02 Feb 2018 09:46:39 +0100
To: mail@mail.com
Subject: =?UTF-8?B?QWxhcm0=?=
MIME-Version: 1.0
Content-Type: multipart/mixed;
       boundary="----=_NextPart_000_0006_01CAB9FA.E6640E80"

This is a multi-part message in MIME format.

------=_NextPart_000_0006_01CAB9FA.E6640E80
Content-Type: text/plain;
    format=flowed;
    charset="utf-8";
    reply-type=original
Content-Transfer-Encoding: 7bit

Date:2/2/2018 9:45:03 AM:02/02 09:45
Category:Information:
Desc:Servers.Main Server:
DescCompl:Project.Management System.Servers.Main Server
Tag:Mail.SMTP Status
TagCompl:Mail.SMTP Status [Mail]
Status:Quiet
EventComplProject.Management System.Servers.Main Server
Message:N/A
Cause:Mail Error (Wrong SMTP Server):Mail Error 
-----
Message ID: -(6138661-
------=_NextPart_000_0006_01CAB9FA.E6640E80--

1 个答案:

答案 0 :(得分:1)

TStream事件提供的OnMsgReceive是远程客户端发送的原始电子邮件数据。但是,它不是TIdMessage.LoadFromStream()默认的格式。具体来说,TIdMessage.LoadFrom...()方法期望数据采用SMTP在传输过程中使用的转义格式。在OnMsgReceive事件被触发之前,该撤消将被撤消。因此,按原样加载TStream 可能导致解析问题。

幸运的是,Indy有一个IdMessageHelper单元来解决这个问题(请参阅New TIdMessage helper,其中更详细地描述了该问题)。它引入了新的LoadFrom...()方法,它们添加了一个AUsesDotTransparency参数,您可以将其设置为False,这样TIdMessage在解析时就不会再期望转义了。

对于电子邮件的内容,多部分MIME正文不存储在TIdMessage.Body属性中,它们存储在TIdMessagePart派生对象中,如TIdTextTIdMessage.MessageParts属性。因此,您需要浏览该属性以查找您感兴趣的文本。

您还必须记住,Indy的大多数基于TCP的服务器都是多线程的。 OnMsgReceive事件在工作线程中触发,因此如果要访问UI,则必须与主UI线程同步。

话虽如此,尝试更像这样的事情:

uses
  ..., IdGlobalProtocols, IdMessageParts, IdText, IdMessage, IdMessageHelper;

procedure TForm1.IdSMTPServer1MsgReceive(ASender: TIdSMTPServerContext;
  AMsg: TStream; var LAction: TIdDataReply);
var
 LMsg : TIdMessage;
 LStream : TFileStream;
 i : integer;
 LMsgPart : TIdMessagePart;
 LText : TIdText;
begin
  // When a message is received by the server, this event fires.
  // The message data is made available in the AMsg : TStream.

  AddToLog('Msg recv ' + FormatDateTime('hh:nn:ss', Now));

  LMsg := TIdMessage.Create;
  try
    //LMsg.LoadFromStream(AMsg);
    LMsg.LoadFromStream(AMsg, False);

    TThread.Synchronize(nil,
      procedure
      begin
        ToLabel.Caption := LMsg.Recipients.EMailAddresses;
        // FYI, the *true* recipients of the email are stored in the
        // ASender.RCPTList property, which may include additional
        // recipients not specified by TIdMessage.Recipients due to BCCs...

        FromLabel.Caption := LMsg.From.Text;
        // FYI, ASender also has a From property, which is the *true* sender
        // of the email, which may be different than what TIdMessage.From
        // says...

        SubjectLabel.Caption := LMsg.Subject;
      end
    );

    if IsHeaderMediaType(LMsg.ContentType, 'multipart') then
    begin
      LText := nil;
      for i := 0 to LMsg.MessageParts.Count-1 do
      begin
        LMsgPart := LMsg.MessageParts[i];
        if (LMsgPart is TIdText) and
           IsHeaderMediaType(LMsgPart.ContentType, 'text/plain') then
        begin
          //AddToLog(LMsgPart);
          LText := TIdText(LMsgPart);
          Break;
        end
      end;
      TThread.Synchronize(nil,
        procedure
        begin
          if LText <> nil then
            Memo1.Lines := LText.Body
          else
            Memo1.Clear;
        end
      );
    end
    else if IsHeaderMediaType(LMsg.ContentType, 'text') then
    begin
      TThread.Synchronize(nil,
        procedure
        begin
          Memo1.Lines := LMsg.Body;
        end
      );
    end else
    begin
      TThread.Synchronize(nil,
        procedure
        begin
          Memo1.Clear;
        end
      );
    end;
  finally
    LMsg.Free;
  end;

  // Just write to a file as in demo
  LStream := TFileStream.Create(ExtractFilePath(Application.ExeName) + Format('test(%d).eml', [mailno]), fmCreate);
  try
    LStream.CopyFrom(AMsg, 0);
  finally
    LStream.Free;
  end;

  // Alternatively, AMsg is a TMemoryStream by default, unless you
  // provide your own TStream in the OnBeforeMsg event...
  //
  // TMemoryStream(AMsg).SaveToFile(ExtractFilePath(Application.ExeName) + Format('test(%d).eml', [mailno]));
end;