restemplate/indy/Protocols/IdMessageBuilder.pas

748 lines
22 KiB
Plaintext

unit IdMessageBuilder;
interface
{$i IdCompilerDefines.inc}
uses
Classes, IdMessage;
type
TIdMessageBuilderAttachment = class(TCollectionItem)
private
FContentID: String;
FContentTransfer: String;
FContentType: String;
FData: TStream;
FFileName: String;
FName: String;
public
procedure Assign(Source: TPersistent); override;
property ContentID: String read FContentID write FContentID;
property ContentTransfer: String read FContentTransfer;
property ContentType: String read FContentType write FContentType;
property Data: TStream read FData write FData;
property FileName: String read FFileName write FFileName;
property Name: String read FName write FName;
end;
TIdMessageBuilderAttachments = class(TCollection)
private
function GetAttachment(Index: Integer): TIdMessageBuilderAttachment;
procedure SetAttachment(Index: Integer; Value: TIdMessageBuilderAttachment);
public
constructor Create; reintroduce;
function Add: TIdMessageBuilderAttachment; reintroduce; overload;
function Add(const AFileName: String; const AContentID: String = ''): TIdMessageBuilderAttachment; overload;
function Add(AData: TStream; const AContentType: String; const AContentID: String = ''): TIdMessageBuilderAttachment; overload;
procedure AddToMessage(AMsg: TIdMessage; ParentPart: Integer);
property Attachment[Index: Integer]: TIdMessageBuilderAttachment
read GetAttachment write SetAttachment; default;
end;
TIdCustomMessageBuilder = class
protected
FAttachments: TIdMessageBuilderAttachments;
FPlainText: TStrings;
FPlainTextCharSet: String;
FPlainTextContentTransfer: String;
procedure AddAttachments(AMsg: TIdMessage);
procedure FillBody(AMsg: TIdMessage); virtual; abstract;
procedure FillHeaders(AMsg: TIdMessage); virtual;
procedure SetPlainText(AValue: TStrings);
procedure SetAttachments(AValue: TIdMessageBuilderAttachments);
public
constructor Create; virtual;
destructor Destroy; override;
//
procedure Clear; virtual;
procedure FillMessage(AMsg: TIdMessage);
function NewMessage(AOwner: TComponent = nil): TIdMessage;
//
property Attachments: TIdMessageBuilderAttachments read FAttachments write SetAttachments;
property PlainText: TStrings read FPlainText write SetPlainText;
property PlainTextCharSet: String read FPlainTextCharSet write FPlainTextCharSet;
property PlainTextContentTransfer: String read FPlainTextContentTransfer write FPlainTextContentTransfer;
end;
TIdMessageBuilderPlain = class(TIdCustomMessageBuilder)
protected
procedure FillBody(AMsg: TIdMessage); override;
procedure FillHeaders(AMsg: TIdMessage); override;
end;
TIdMessageBuilderHtml = class(TIdCustomMessageBuilder)
protected
FHtml: TStrings;
FHtmlCharSet: String;
FHtmlContentTransfer: String;
FHtmlFiles: TIdMessageBuilderAttachments;
FHtmlViewerNeededMsg: String;
procedure FillBody(AMsg: TIdMessage); override;
procedure FillHeaders(AMsg: TIdMessage); override;
procedure SetHtml(AValue: TStrings);
procedure SetHtmlFiles(AValue: TIdMessageBuilderAttachments);
public
constructor Create; override;
destructor Destroy; override;
//
procedure Clear; override;
//
property Html: TStrings read FHtml write SetHtml;
property HtmlCharSet: String read FHtmlCharSet write FHtmlCharSet;
property HtmlContentTransfer: String read FHtmlContentTransfer write FHtmlContentTransfer;
property HtmlFiles: TIdMessageBuilderAttachments read FHtmlFiles write SetHtmlFiles;
property HtmlViewerNeededMsg: String read FHtmlViewerNeededMsg write FHtmlViewerNeededMsg;
end;
TIdMessageBuilderRtfType = (idMsgBldrRtfMS, idMsgBldrRtfEnriched, idMsgBldrRtfRichtext);
TIdMessageBuilderRtf = class(TIdCustomMessageBuilder)
protected
FRtf: TStrings;
FRtfType: TIdMessageBuilderRtfType;
FRtfViewerNeededMsg: String;
procedure FillBody(AMsg: TIdMessage); override;
procedure FillHeaders(AMsg: TIdMessage); override;
procedure SetRtf(AValue: TStrings);
public
constructor Create; override;
destructor Destroy; override;
//
procedure Clear; override;
//
property Rtf: TStrings read FRtf write SetRtf;
property RtfType: TIdMessageBuilderRtfType read FRtfType write FRtfType;
property RtfViewerNeededMsg: String read FRtfViewerNeededMsg write FRtfViewerNeededMsg;
end;
implementation
uses
IdGlobal, IdGlobalProtocols, IdMessageParts, IdAttachment, IdAttachmentFile,
IdAttachmentMemory, IdResourceStringsProtocols, IdText, SysUtils;
const
cTextPlain = 'text/plain'; {do not localize}
cTextHtml = 'text/html'; {do not localize}
cTextRtf: array[TIdMessageBuilderRtfType] of String = ('text/rtf', 'text/enriched', 'text/richtext'); {do not localize}
cMultipartAlternative = 'multipart/alternative'; {do not localize}
cMultipartMixed = 'multipart/mixed'; {do not localize}
cMultipartRelatedHtml = 'multipart/related; type="text/html"'; {do not localize}
{ TIdMessageBuilderAttachment }
procedure TIdMessageBuilderAttachment.Assign(Source: TPersistent);
var
LSource: TIdMessageBuilderAttachment;
begin
if Source is TIdMessageBuilderAttachment then
begin
LSource := TIdMessageBuilderAttachment(Source);
FContentID := LSource.FContentID;
FContentTransfer := LSource.FContentTransfer;
FContentType := LSource.FContentType;
FData := LSource.FData;
FFileName := LSource.FFileName;
FName := LSource.FName;
end else begin
inherited Assign(Source);
end;
end;
{ TIdMessageBuilderAttachments }
constructor TIdMessageBuilderAttachments.Create;
begin
inherited Create(TIdMessageBuilderAttachment);
end;
function TIdMessageBuilderAttachments.Add: TIdMessageBuilderAttachment;
begin
// This helps prevent unsupported TIdMessageBuilderAttachment from being added
Result := nil;
end;
function TIdMessageBuilderAttachments.Add(const AFileName: String;
const AContentID: String = ''): TIdMessageBuilderAttachment;
begin
Result := TIdMessageBuilderAttachment(inherited Add);
Result.FContentID := AContentID;
Result.FFileName := AFileName;
end;
function TIdMessageBuilderAttachments.Add(AData: TStream; const AContentType: String;
const AContentID: String = ''): TIdMessageBuilderAttachment;
begin
Assert(AData <> nil);
Result := TIdMessageBuilderAttachment(inherited Add);
Result.FContentID := AContentID;
Result.FContentType := AContentType;
Result.FData := AData;
end;
procedure TIdMessageBuilderAttachments.AddToMessage(AMsg: TIdMessage; ParentPart: Integer);
var
I: Integer;
LMsgBldrAttachment: TIdMessageBuilderAttachment;
LMsgAttachment: TIdAttachment;
LStream: TStream;
function FormatContentId(Item: TIdMessageBuilderAttachment): String;
begin
if Item.ContentID <> '' then begin
Result := EnsureMsgIDBrackets(Item.ContentID);
end else begin
Result := '';
end;
end;
function FormatContentType(Item: TIdMessageBuilderAttachment): String;
begin
if Item.ContentType <> '' then begin
Result := Item.ContentType;
end else begin
Result := GetMIMETypeFromFile(Item.FileName);
end;
end;
function FormatName(Item: TIdMessageBuilderAttachment): String;
begin
if Item.Name <> '' then begin
Result := Item.Name;
end
else if Item.FileName <> '' then begin
Result := ExtractFileName(Item.FileName);
end else begin
Result := '';
end;
end;
begin
for I := 0 to Count-1 do
begin
LMsgBldrAttachment := Attachment[I];
if Assigned(LMsgBldrAttachment.Data) then
begin
LMsgAttachment := TIdAttachmentMemory.Create(AMsg.MessageParts);
try
LMsgAttachment.FileName := ExtractFileName(LMsgBldrAttachment.FileName);
LStream := LMsgAttachment.PrepareTempStream;
try
LStream.CopyFrom(LMsgBldrAttachment.Data, 0);
finally
LMsgAttachment.FinishTempStream;
end;
except
LMsgAttachment.Free;
raise;
end;
end else
begin
LMsgAttachment := TIdAttachmentFile.Create(AMsg.MessageParts, LMsgBldrAttachment.FileName);
end;
LMsgAttachment.Name := FormatName(LMsgBldrAttachment);
LMsgAttachment.ContentId := FormatContentId(LMsgBldrAttachment);
LMsgAttachment.ContentType := FormatContentType(LMsgBldrAttachment);
LMsgAttachment.ContentTransfer := LMsgBldrAttachment.ContentTransfer;
if ParentPart > -1 then
begin
if IsHeaderMediaType(LMsgAttachment.ContentType, 'image') then begin {do not localize}
LMsgAttachment.ContentDisposition := 'inline'; {do not localize}
end;
LMsgAttachment.ParentPart := ParentPart;
end;
end;
end;
function TIdMessageBuilderAttachments.GetAttachment(Index: Integer): TIdMessageBuilderAttachment;
begin
Result := TIdMessageBuilderAttachment(inherited GetItem(Index));
end;
procedure TIdMessageBuilderAttachments.SetAttachment(Index: Integer; Value: TIdMessageBuilderAttachment);
begin
inherited SetItem(Index, Value);
end;
{ TIdCustomMessageBuilder }
constructor TIdCustomMessageBuilder.Create;
begin
inherited Create;
FPlainText := TStringList.Create;
FAttachments := TIdMessageBuilderAttachments.Create;
end;
destructor TIdCustomMessageBuilder.Destroy;
begin
FPlainText.Free;
FAttachments.Free;
inherited Destroy;
end;
procedure TIdCustomMessageBuilder.AddAttachments(AMsg: TIdMessage);
begin
FAttachments.AddToMessage(AMsg, -1);
end;
procedure TIdCustomMessageBuilder.Clear;
begin
FAttachments.Clear;
FPlainText.Clear;
FPlainTextCharSet := '';
FPlainTextContentTransfer := '';
end;
procedure TIdCustomMessageBuilder.FillMessage(AMsg: TIdMessage);
begin
if not Assigned(AMsg) then begin
Exit;
end;
// Clear only the body, ContentType, CharSet, and ContentTransferEncoding here...
//
AMsg.ClearBody;
AMsg.ContentType := '';
AMsg.CharSet := '';
AMsg.ContentTransferEncoding := '';
// let the message decide how to encode itself
// based on what parts are added in InternalFill()
//
AMsg.Encoding := meDefault;
// fill in type-specific content first
//
FillBody(AMsg);
// Are non-related attachments present?
//
AddAttachments(AMsg);
// Determine the top-level ContentType and
// ContentTransferEncoding for the message now
//
FillHeaders(AMsg);
end;
function TIdCustomMessageBuilder.NewMessage(AOwner: TComponent = nil): TIdMessage;
begin
Result := TIdMessage.Create(AOwner);
try
FillMessage(Result);
except
FreeAndNil(Result);
raise;
end;
end;
procedure TIdCustomMessageBuilder.SetAttachments(AValue: TIdMessageBuilderAttachments);
begin
FAttachments.Assign(AValue);
end;
procedure TIdCustomMessageBuilder.FillHeaders(AMsg: TIdMessage);
var
LPart: TIdMessagePart;
begin
if FAttachments.Count > 0 then
begin
if AMsg.MessageParts.Count > 1 then
begin
// plain text and/or formatting, and at least 1 non-related attachment
//
AMsg.ContentType := cMultipartMixed;
AMsg.CharSet := '';
AMsg.ContentTransferEncoding := '';
end else
begin
// no plain text or formatting, only 1 non-related attachment
//
LPart := AMsg.MessageParts[0];
AMsg.ContentType := LPart.ContentType;
AMsg.CharSet := LPart.CharSet;
AMsg.ContentTransferEncoding := LPart.ContentTransfer;
end;
end else
begin
AMsg.ContentType := '';
AMsg.CharSet := '';
AMsg.ContentTransferEncoding := '';
end;
end;
procedure TIdCustomMessageBuilder.SetPlainText(AValue: TStrings);
begin
FPlainText.Assign(AValue);
end;
{ TIdMessageBuilderPlain }
procedure TIdMessageBuilderPlain.FillBody(AMsg: TIdMessage);
var
LTextPart: TIdText;
begin
// Is plain text present?
//
if FPlainText.Count > 0 then
begin
// Should the message contain only plain text?
//
if FAttachments.Count = 0 then
begin
AMsg.Body.Assign(FPlainText);
end else
begin
// At this point, multiple pieces will be present in the message
// body, so everything must be stored in the MessageParts collection...
//
LTextPart := TIdText.Create(AMsg.MessageParts, FPlainText);
LTextPart.ContentType := cTextPlain;
LTextPart.CharSet := FPlainTextCharSet;
LTextPart.ContentTransfer := FPlainTextContentTransfer;
end;
end;
end;
procedure TIdMessageBuilderPlain.FillHeaders(AMsg: TIdMessage);
begin
if (FPlainText.Count > 0) and (FAttachments.Count = 0) then
begin
// plain text only
//
AMsg.ContentType := cTextPlain;
AMsg.CharSet := FPlainTextCharSet;
AMsg.ContentTransferEncoding := FPlainTextContentTransfer;
end else
begin
inherited FillHeaders(AMsg);
end;
end;
{ TIdMessageBuilderHtml }
constructor TIdMessageBuilderHtml.Create;
begin
inherited Create;
FHtml := TStringList.Create;
FHtmlFiles := TIdMessageBuilderAttachments.Create;
FHtmlViewerNeededMsg := rsHtmlViewerNeeded;
end;
destructor TIdMessageBuilderHtml.Destroy;
begin
FHtml.Free;
FHtmlFiles.Free;
inherited Destroy;
end;
procedure TIdMessageBuilderHtml.Clear;
begin
FHtml.Clear;
FHtmlCharSet := '';
FHtmlContentTransfer := '';
FHtmlFiles.Clear;
inherited Clear;
end;
procedure TIdMessageBuilderHtml.FillBody(AMsg: TIdMessage);
var
LUsePlain, LUseHtml, LUseHtmlFiles, LUseAttachments: Boolean;
LAlternativeIndex, LRelatedIndex: Integer;
LTextPart: TIdText;
begin
// Cache these for better performance
//
LUsePlain := FPlainText.Count > 0;
LUseHtml := FHtml.Count > 0;
LUseHtmlFiles := LUseHtml and (FHtmlFiles.Count > 0);
LUseAttachments := FAttachments.Count > 0;
LAlternativeIndex := -1;
LRelatedIndex := -1;
// Is any body data present at all?
//
if not (LUsePlain or LUseHtml or LUseHtmlFiles or LUseAttachments) then begin
Exit;
end;
// Should the message contain only plain text?
//
if LUsePlain and not (LUseHtml or LUseAttachments) then
begin
AMsg.Body.Assign(FPlainText);
Exit;
end;
// Should the message contain only HTML?
//
if LUseHtml and not (LUsePlain or LUseHtmlFiles or LUseAttachments) then
begin
// TODO: create "multipart/alternative" pieces if FHtmlViewerNeededMsg is not empty...
AMsg.Body.Assign(FHtml);
Exit;
end;
// At this point, multiple pieces will be present in the message
// body, so everything must be stored in the MessageParts collection...
// If the message should contain both plain text and HTML, a
// "multipart/alternative" piece is needed to wrap them if
// non-related attachments are also present...
//
// RLebeau 5/23/2011: need to output the Alternative piece if
// the "HTML Viewer is needed" text is going to be used...
//
if {LUsePlain and} LUseHtml and LUseAttachments then
begin
LTextPart := TIdText.Create(AMsg.MessageParts, nil);
LTextPart.ContentType := cMultipartAlternative;
LAlternativeIndex := LTextPart.Index;
end;
// Is plain text present?
//
if LUsePlain or LUseHtml then
begin
LTextPart := TIdText.Create(AMsg.MessageParts, FPlainText);
begin
if LUseHtml and (not LUsePlain) then
begin
LTextPart.Body.Text := FHtmlViewerNeededMsg;
end;
LTextPart.ContentType := cTextPlain;
LTextPart.CharSet := FPlainTextCharSet;
LTextPart.ContentTransfer := FPlainTextContentTransfer;
LTextPart.ParentPart := LAlternativeIndex;
end;
end;
// Is HTML present?
//
if LUseHtml then
begin
// related attachments can't be referenced by, or used inside
// of, plain text, so there is no point in wrapping the plain
// text inside the same "multipart/related" part with the HTML
// and attachments. Some email programs don't do that as well.
// This logic is newer and more accurate than what is described
// in the "HTML Messages" article found on Indy's website.
//
if LUseHtmlFiles then
begin
LTextPart := TIdText.Create(AMsg.MessageParts, nil);
LTextPart.ContentType := cMultipartRelatedHtml;
LTextPart.ParentPart := LAlternativeIndex;
LRelatedIndex := LTextPart.Index;
end;
// Add HTML
//
LTextPart := TIdText.Create(AMsg.MessageParts, FHtml);
LTextPart.ContentType := cTextHtml;
LTextPart.CharSet := FHtmlCharSet;
LTextPart.ContentTransfer := FHtmlContentTransfer;
if LRelatedIndex <> -1 then begin
LTextPart.ParentPart := LRelatedIndex; // plain text and related attachments
end else begin
LTextPart.ParentPart := LAlternativeIndex; // plain text and optional non-related attachments
end;
// Are related attachments present?
//
if LUseHtmlFiles then begin
FHtmlFiles.AddToMessage(AMsg, LRelatedIndex);
end;
end;
end;
procedure TIdMessageBuilderHtml.FillHeaders(AMsg: TIdMessage);
begin
if FAttachments.Count = 0 then
begin
if (FPlainText.Count > 0) and (FHtml.Count = 0) then
begin
// plain text only
//
AMsg.ContentType := cTextPlain;
AMsg.CharSet := FPlainTextCharSet;
AMsg.ContentTransferEncoding := FPlainTextContentTransfer;
end
else if FHtml.Count > 0 then
begin
if (FPlainText.Count = 0) and (FHtmlFiles.Count = 0) then
begin
// HTML only
//
AMsg.ContentType := cTextHtml;
AMsg.CharSet := FHtmlCharSet;
AMsg.ContentTransferEncoding := FHtmlContentTransfer;
end else
begin
// plain text and HTML and no related attachments
//
AMsg.ContentType := cMultipartAlternative;
AMsg.CharSet := '';
AMsg.ContentTransferEncoding := '';
end;
end;
end else
begin
inherited FillHeaders(AMsg);
end;
end;
procedure TIdMessageBuilderHtml.SetHtml(AValue: TStrings);
begin
FHtml.Assign(AValue);
end;
procedure TIdMessageBuilderHtml.SetHtmlFiles(AValue: TIdMessageBuilderAttachments);
begin
FHtmlFiles.Assign(AValue);
end;
{ TIdMessageBuilderRTF }
constructor TIdMessageBuilderRtf.Create;
begin
inherited Create;
FRtf := TStringList.Create;
FRtfType := idMsgBldrRtfMS;
FRtfViewerNeededMsg := rsRtfViewerNeeded;
end;
destructor TIdMessageBuilderRtf.Destroy;
begin
FRtf.Free;
inherited Destroy;
end;
procedure TIdMessageBuilderRtf.Clear;
begin
FRtf.Clear;
inherited Clear;
end;
procedure TIdMessageBuilderRtf.FillBody(AMsg: TIdMessage);
var
LUsePlain, LUseRtf, LUseAttachments: Boolean;
LAlternativeIndex: Integer;
LTextPart: TIdText;
begin
// Cache these for better performance
//
LUsePlain := FPlainText.Count > 0;
LUseRtf := FRtf.Count > 0;
LUseAttachments := FAttachments.Count > 0;
LAlternativeIndex := -1;
// Is any body data present at all?
//
if not (LUsePlain or LUseRtf or LUseAttachments) then begin
Exit;
end;
// Should the message contain only plain text?
//
if LUsePlain and not (LUseRtf or LUseAttachments) then
begin
AMsg.Body.Assign(FPlainText);
Exit;
end;
// Should the message contain only RTF?
//
if LUseRtf and not (LUsePlain or LUseAttachments) then
begin
// TODO: create "multipart/alternative" pieces if FRtfViewerNeededMsg is not empty...
AMsg.Body.Assign(FRtf);
Exit;
end;
// At this point, multiple pieces will be present in the message
// body, so everything must be stored in the MessageParts collection...
// If the message should contain both plain text and RTF, a
// "multipart/alternative" piece is needed to wrap them if
// attachments are also present...
//
// RLebeau 11/11/2013: need to output the Alternative piece if
// the "RTF Viewer is needed" text is going to be used...
//
if {LUsePlain and} LUseRtf and LUseAttachments then
begin
LTextPart := TIdText.Create(AMsg.MessageParts, nil);
LTextPart.ContentType := cMultipartAlternative;
LAlternativeIndex := LTextPart.Index;
end;
// Is plain text present?
//
if LUsePlain or LUseRtf then
begin
LTextPart := TIdText.Create(AMsg.MessageParts, FPlainText);
if LUseRtf and (not LUsePlain) then
begin
LTextPart.Body.Text := FRtfViewerNeededMsg;
end;
LTextPart.ContentType := cTextPlain;
LTextPart.CharSet := FPlainTextCharSet;
LTextPart.ContentTransfer := FPlainTextContentTransfer;
LTextPart.ParentPart := LAlternativeIndex;
end;
// Is RTF present?
//
if LUseRtf then
begin
// Add RTF
//
LTextPart := TIdText.Create(AMsg.MessageParts, FRtf);
LTextPart.ContentType := cTextRtf[FRtfType];
LTextPart.ParentPart := LAlternativeIndex; // plain text and optional non-related attachments
end;
end;
procedure TIdMessageBuilderRtf.FillHeaders(AMsg: TIdMessage);
begin
if FAttachments.Count = 0 then
begin
if (FPlainText.Count > 0) and (FRtf.Count = 0) then
begin
// plain text only
//
AMsg.ContentType := cTextPlain;
AMsg.CharSet := FPlainTextCharSet;
AMsg.ContentTransferEncoding := FPlainTextContentTransfer;
end
else if (FRtf.Count > 0) and (FPlainText.Count = 0) then
begin
// RTF only
//
AMsg.ContentType := cTextRtf[FRtfType];
AMsg.CharSet := '';
AMsg.ContentTransferEncoding := '';
end else
begin
// plain text and RTF and no non-related attachments
//
AMsg.ContentType := cMultipartAlternative;
AMsg.CharSet := '';
AMsg.ContentTransferEncoding := '';
end;
end else
begin
inherited FillHeaders(AMsg);
end;
end;
procedure TIdMessageBuilderRtf.SetRtf(AValue: TStrings);
begin
FRtf.Assign(AValue);
end;
end.