mirror of
https://gitlab.com/freepascal.org/fpc/source.git
synced 2025-04-22 18:29:23 +02:00
987 lines
26 KiB
ObjectPascal
987 lines
26 KiB
ObjectPascal
{ lNet SMTP unit
|
|
|
|
CopyRight (C) 2005-2008 Ales Katona
|
|
|
|
This library is Free software; you can rediStribute it and/or modify it
|
|
under the terms of the GNU Library General Public License as published by
|
|
the Free Software Foundation; either version 2 of the License, or (at your
|
|
option) any later version.
|
|
|
|
This program is diStributed in the hope that it will be useful, but WITHOUT
|
|
ANY WARRANTY; withOut even the implied warranty of MERCHANTABILITY or
|
|
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License
|
|
for more details.
|
|
|
|
You should have received a Copy of the GNU Library General Public License
|
|
along with This library; if not, Write to the Free Software Foundation,
|
|
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
|
|
This license has been modified. See File LICENSE.ADDON for more inFormation.
|
|
Should you find these sources without a LICENSE File, please contact
|
|
me at ales@chello.sk
|
|
}
|
|
|
|
unit lsmtp;
|
|
|
|
{$mode objfpc}{$H+}
|
|
{$inline on}
|
|
|
|
interface
|
|
|
|
uses
|
|
Classes, SysUtils, Contnrs, Base64,
|
|
lNet, lEvents, lCommon, lMimeWrapper, lMimeStreams;
|
|
|
|
type
|
|
TLSMTP = class;
|
|
TLSMTPClient = class;
|
|
|
|
TLSMTPStatus = (ssNone, ssCon, ssHelo, ssEhlo, ssAuthLogin, ssAuthPlain,
|
|
ssStartTLS, ssMail, ssRcpt, ssData, ssRset, ssQuit, ssLast);
|
|
|
|
TLSMTPStatusSet = set of TLSMTPStatus;
|
|
|
|
TLSMTPStatusRec = record
|
|
Status: TLSMTPStatus;
|
|
Args: array[1..2] of string;
|
|
end;
|
|
|
|
{ TLSMTPStatusFront }
|
|
{$DEFINE __front_type__ := TLSMTPStatusRec}
|
|
{$i lcontainersh.inc}
|
|
TLSMTPStatusFront = TLFront;
|
|
|
|
TLSMTPClientStatusEvent = procedure (aSocket: TLSocket;
|
|
const aStatus: TLSMTPStatus) of object;
|
|
|
|
{ TMail }
|
|
|
|
TMail = class
|
|
protected
|
|
FMailText: string;
|
|
FMailStream: TMimeStream;
|
|
FRecipients: string;
|
|
FSender: string;
|
|
FSubject: string;
|
|
function GetCount: Integer;
|
|
function GetSection(i: Integer): TMimeSection;
|
|
procedure SetSection(i: Integer; const AValue: TMimeSection);
|
|
public
|
|
constructor Create;
|
|
destructor Destroy; override;
|
|
procedure AddTextSection(const aText: string; const aCharSet: string = 'UTF-8');
|
|
procedure AddFileSection(const aFileName: string);
|
|
procedure AddStreamSection(aStream: TStream; const FreeStream: Boolean = False);
|
|
procedure DeleteSection(const i: Integer);
|
|
procedure RemoveSection(aSection: TMimeSection);
|
|
procedure Reset;
|
|
public
|
|
property MailText: string read FMailText write FMailText; deprecated; // use sections!
|
|
property Sender: string read FSender write FSender;
|
|
property Recipients: string read FRecipients write FRecipients;
|
|
property Subject: string read FSubject write FSubject;
|
|
property Sections[i: Integer]: TMimeSection read GetSection write SetSection; default;
|
|
property SectionCount: Integer read GetCount;
|
|
end;
|
|
|
|
TLSMTP = class(TLComponent)
|
|
protected
|
|
FConnection: TLTcp;
|
|
FFeatureList: TStringList;
|
|
protected
|
|
function GetTimeout: Integer;
|
|
procedure SetTimeout(const AValue: Integer);
|
|
|
|
function GetSession: TLSession;
|
|
procedure SetSession(const AValue: TLSession);
|
|
procedure SetCreator(AValue: TLComponent); override;
|
|
|
|
function GetConnected: Boolean;
|
|
|
|
function GetSocketClass: TLSocketClass;
|
|
procedure SetSocketClass(const AValue: TLSocketClass);
|
|
|
|
function GetEventer: TLEventer;
|
|
procedure SetEventer(Value: TLEventer);
|
|
public
|
|
constructor Create(aOwner: TComponent); override;
|
|
destructor Destroy; override;
|
|
|
|
function HasFeature(aFeature: string): Boolean;
|
|
public
|
|
property Connected: Boolean read GetConnected;
|
|
property Connection: TLTcp read FConnection;
|
|
|
|
property SocketClass: TLSocketClass read GetSocketClass write SetSocketClass;
|
|
property Eventer: TLEventer read GetEventer write SetEventer;
|
|
property Timeout: Integer read GetTimeout write SetTimeout;
|
|
property Session: TLSession read GetSession write SetSession;
|
|
property FeatureList: TStringList read FFeatureList;
|
|
end;
|
|
|
|
{ TLSMTPClient }
|
|
|
|
TLSMTPClient = class(TLSMTP, ILClient)
|
|
protected
|
|
FStatus: TLSMTPStatusFront;
|
|
FCommandFront: TLSMTPStatusFront;
|
|
FPipeLine: Boolean;
|
|
FAuthStep: Integer;
|
|
|
|
FOnConnect: TLSocketEvent;
|
|
FOnReceive: TLSocketEvent;
|
|
FOnDisconnect: TLSocketEvent;
|
|
FOnSuccess: TLSMTPClientStatusEvent;
|
|
FOnFailure: TLSMTPClientStatusEvent;
|
|
FOnError: TLSocketErrorEvent;
|
|
FOnSent: TLSocketProgressEvent;
|
|
|
|
FSL: TStringList;
|
|
FStatusSet: TLSMTPStatusSet;
|
|
FBuffer: string;
|
|
FDataBuffer: string; // intermediate wait buffer on DATA command
|
|
FTempBuffer: string; // used independently from FBuffer for feature list
|
|
FCharCount: Integer; // count of chars from last CRLF
|
|
FStream: TStream;
|
|
protected
|
|
procedure OnEr(const msg: string; aSocket: TLSocket);
|
|
procedure OnRe(aSocket: TLSocket);
|
|
procedure OnCo(aSocket: TLSocket);
|
|
procedure OnDs(aSocket: TLSocket);
|
|
procedure OnCs(aSocket: TLSocket);
|
|
protected
|
|
function CanContinue(const aStatus: TLSMTPStatus; const Arg1, Arg2: string): Boolean;
|
|
|
|
function CleanInput(var s: string): Integer;
|
|
|
|
procedure EvaluateServer;
|
|
procedure EvaluateFeatures;
|
|
procedure EvaluateAnswer(const Ans: string);
|
|
procedure ExecuteFrontCommand;
|
|
|
|
procedure AddToBuffer(s: string);
|
|
procedure SendData(const FromStream: Boolean = False);
|
|
function EncodeBase64(const s: string): string;
|
|
public
|
|
constructor Create(aOwner: TComponent); override;
|
|
destructor Destroy; override;
|
|
|
|
function Connect(const aHost: string; const aPort: Word = 25): Boolean; virtual; overload;
|
|
function Connect: Boolean; virtual; overload;
|
|
|
|
function Get(var aData; const aSize: Integer; aSocket: TLSocket = nil): Integer; virtual;
|
|
function GetMessage(out msg: string; aSocket: TLSocket = nil): Integer; virtual;
|
|
|
|
procedure SendMail(From, Recipients, Subject, Msg: string);
|
|
procedure SendMail(From, Recipients, Subject: string; aStream: TStream);
|
|
procedure SendMail(aMail: TMail);
|
|
|
|
procedure Helo(aHost: string = '');
|
|
procedure Ehlo(aHost: string = '');
|
|
procedure StartTLS;
|
|
procedure AuthLogin(aName, aPass: string);
|
|
procedure AuthPlain(aName, aPass: string);
|
|
procedure Mail(const From: string);
|
|
procedure Rcpt(const RcptTo: string);
|
|
procedure Data(const Msg: string);
|
|
procedure Rset;
|
|
procedure Quit;
|
|
|
|
procedure Disconnect(const Forced: Boolean = True); override;
|
|
|
|
procedure CallAction; override;
|
|
public
|
|
property PipeLine: Boolean read FPipeLine write FPipeLine;
|
|
property StatusSet: TLSMTPStatusSet read FStatusSet write FStatusSet;
|
|
property OnConnect: TLSocketEvent read FOnConnect write FOnConnect;
|
|
property OnReceive: TLSocketEvent read FOnReceive write FOnReceive;
|
|
property OnDisconnect: TLSocketEvent read FOnDisconnect write FOnDisconnect;
|
|
property OnSuccess: TLSMTPClientStatusEvent read FOnSuccess write FOnSuccess;
|
|
property OnFailure: TLSMTPClientStatusEvent read FOnFailure write FOnFailure;
|
|
property OnError: TLSocketErrorEvent read FOnError write FOnError;
|
|
property OnSent: TLSocketProgressEvent read FOnSent write FOnSent;
|
|
end;
|
|
|
|
implementation
|
|
|
|
const
|
|
EMPTY_REC: TLSMTPStatusRec = (Status: ssNone; Args: ('', ''));
|
|
|
|
{$i lcontainers.inc}
|
|
|
|
function StatusToStr(const aStatus: TLSMTPStatus): string;
|
|
const
|
|
STATAR: array[ssNone..ssLast] of string = ('ssNone', 'ssCon', 'ssHelo', 'ssEhlo',
|
|
'ssStartTLS', 'ssAuthLogin', 'ssAuthPlain',
|
|
'ssMail', 'ssRcpt', 'ssData', 'ssRset', 'ssQuit', 'ssLast');
|
|
begin
|
|
Result := STATAR[aStatus];
|
|
end;
|
|
|
|
function MakeStatusRec(const aStatus: TLSMTPStatus; const Arg1, Arg2: string): TLSMTPStatusRec;
|
|
begin
|
|
Result.Status := aStatus;
|
|
Result.Args[1] := Arg1;
|
|
Result.Args[2] := Arg2;
|
|
end;
|
|
|
|
{ TLSMTP }
|
|
|
|
function TLSMTP.GetSession: TLSession;
|
|
begin
|
|
Result := FConnection.Session;
|
|
end;
|
|
|
|
procedure TLSMTP.SetSession(const AValue: TLSession);
|
|
begin
|
|
FConnection.Session := aValue;
|
|
end;
|
|
|
|
procedure TLSMTP.SetCreator(AValue: TLComponent);
|
|
begin
|
|
inherited SetCreator(AValue);
|
|
|
|
FConnection.Creator := AValue;
|
|
end;
|
|
|
|
function TLSMTP.GetTimeout: Integer;
|
|
begin
|
|
Result := FConnection.Timeout;
|
|
end;
|
|
|
|
procedure TLSMTP.SetTimeout(const AValue: Integer);
|
|
begin
|
|
FConnection.Timeout := aValue;
|
|
end;
|
|
|
|
function TLSMTP.GetConnected: Boolean;
|
|
begin
|
|
Result := FConnection.Connected;
|
|
end;
|
|
|
|
function TLSMTP.GetSocketClass: TLSocketClass;
|
|
begin
|
|
Result := FConnection.SocketClass;
|
|
end;
|
|
|
|
procedure TLSMTP.SetSocketClass(const AValue: TLSocketClass);
|
|
begin
|
|
FConnection.SocketClass := AValue;
|
|
end;
|
|
|
|
function TLSMTP.GetEventer: TLEventer;
|
|
begin
|
|
Result := FConnection.Eventer;
|
|
end;
|
|
|
|
procedure TLSMTP.SetEventer(Value: TLEventer);
|
|
begin
|
|
FConnection.Eventer := Value;
|
|
end;
|
|
|
|
constructor TLSMTP.Create(aOwner: TComponent);
|
|
begin
|
|
inherited Create(aOwner);
|
|
|
|
FFeatureList := TStringList.Create;
|
|
FConnection := TLTcp.Create(nil);
|
|
FConnection.Creator := Self;
|
|
// TODO: rework to use the new TLSocketTCP
|
|
FConnection.SocketClass := TLSocket;
|
|
end;
|
|
|
|
destructor TLSMTP.Destroy;
|
|
begin
|
|
FFeatureList.Free;
|
|
FConnection.Free;
|
|
|
|
inherited Destroy;
|
|
end;
|
|
|
|
function TLSMTP.HasFeature(aFeature: string): Boolean;
|
|
var
|
|
tmp: TStringList;
|
|
i, j: Integer;
|
|
AllArgs: Boolean;
|
|
begin
|
|
Result := False;
|
|
try
|
|
tmp := TStringList.Create;
|
|
aFeature := UpperCase(aFeature);
|
|
aFeature := StringReplace(aFeature, ' ', ',', [rfReplaceAll]);
|
|
tmp.CommaText := aFeature;
|
|
for i := 0 to FFeatureList.Count - 1 do begin
|
|
if Pos(tmp[0], FFeatureList[i]) = 1 then begin
|
|
if tmp.Count = 1 then // no arguments, feature found, just exit true
|
|
Exit(True)
|
|
else begin // check arguments
|
|
AllArgs := True;
|
|
for j := 1 to tmp.Count - 1 do
|
|
if Pos(tmp[j], FFeatureList[i]) <= 0 then begin // some argument not found
|
|
AllArgs := False;
|
|
Break;
|
|
end;
|
|
if AllArgs then
|
|
Exit(True);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
finally
|
|
tmp.Free;
|
|
end;
|
|
end;
|
|
|
|
{ TLSMTPClient }
|
|
|
|
constructor TLSMTPClient.Create(aOwner: TComponent);
|
|
begin
|
|
inherited Create(aOwner);
|
|
FPort := 25;
|
|
FStatusSet := [ssNone..ssLast]; // full set
|
|
FSL := TStringList.Create;
|
|
// {$warning TODO: fix pipelining support when server does it}
|
|
FPipeLine := False;
|
|
|
|
FConnection.OnError := @OnEr;
|
|
FConnection.OnCanSend := @OnCs;
|
|
FConnection.OnReceive := @OnRe;
|
|
FConnection.OnConnect := @OnCo;
|
|
FConnection.OnDisconnect := @OnDs;
|
|
|
|
FStatus := TLSMTPStatusFront.Create(EMPTY_REC);
|
|
FCommandFront := TLSMTPStatusFront.Create(EMPTY_REC);
|
|
end;
|
|
|
|
destructor TLSMTPClient.Destroy;
|
|
begin
|
|
if FConnection.Connected then
|
|
Quit;
|
|
FSL.Free;
|
|
FStatus.Free;
|
|
FCommandFront.Free;
|
|
|
|
inherited Destroy;
|
|
end;
|
|
|
|
procedure TLSMTPClient.OnEr(const msg: string; aSocket: TLSocket);
|
|
begin
|
|
if Assigned(FOnFailure) then begin
|
|
while not FStatus.Empty do
|
|
FOnFailure(aSocket, FStatus.Remove.Status);
|
|
end else
|
|
FStatus.Clear;
|
|
|
|
if Assigned(FOnError) then
|
|
FOnError(msg, aSocket);
|
|
end;
|
|
|
|
procedure TLSMTPClient.OnRe(aSocket: TLSocket);
|
|
begin
|
|
if Assigned(FOnReceive) then
|
|
FOnReceive(aSocket);
|
|
end;
|
|
|
|
procedure TLSMTPClient.OnCo(aSocket: TLSocket);
|
|
begin
|
|
if Assigned(FOnConnect) then
|
|
FOnConnect(aSocket);
|
|
end;
|
|
|
|
procedure TLSMTPClient.OnDs(aSocket: TLSocket);
|
|
begin
|
|
if Assigned(FOnDisconnect) then
|
|
FOnDisconnect(aSocket);
|
|
end;
|
|
|
|
procedure TLSMTPClient.OnCs(aSocket: TLSocket);
|
|
begin
|
|
SendData(FStatus.First.Status = ssData);
|
|
end;
|
|
|
|
function TLSMTPClient.CanContinue(const aStatus: TLSMTPStatus; const Arg1, Arg2: string): Boolean;
|
|
begin
|
|
Result := FPipeLine or FStatus.Empty;
|
|
if not Result then
|
|
FCommandFront.Insert(MakeStatusRec(aStatus, Arg1, Arg2));
|
|
end;
|
|
|
|
function TLSMTPClient.CleanInput(var s: string): Integer;
|
|
var
|
|
i: Integer;
|
|
begin
|
|
FSL.Text := s;
|
|
|
|
case FStatus.First.Status of // TODO: clear this to a proper place, the whole thing needs an overhaul
|
|
ssCon,
|
|
ssEhlo: FTempBuffer := FTempBuffer + UpperCase(s);
|
|
end;
|
|
|
|
if FSL.Count > 0 then
|
|
for i := 0 to FSL.Count - 1 do
|
|
if Length(FSL[i]) > 0 then EvaluateAnswer(FSL[i]);
|
|
s := StringReplace(s, CRLF, LineEnding, [rfReplaceAll]);
|
|
i := Pos('PASS', s);
|
|
if i > 0 then
|
|
s := Copy(s, 1, i-1) + 'PASS';
|
|
Result := Length(s);
|
|
end;
|
|
|
|
procedure TLSMTPClient.EvaluateServer;
|
|
begin
|
|
FFeatureList.Clear;
|
|
if Length(FTempBuffer) = 0 then
|
|
Exit;
|
|
|
|
if Pos('ESMTP', FTempBuffer) > 0 then
|
|
FFeatureList.Append('EHLO');
|
|
FTempBuffer := '';
|
|
end;
|
|
|
|
procedure TLSMTPClient.EvaluateFeatures;
|
|
var
|
|
i: Integer;
|
|
begin
|
|
FFeatureList.Clear;
|
|
if Length(FTempBuffer) = 0 then
|
|
Exit;
|
|
|
|
FFeatureList.Text := FTempBuffer;
|
|
FTempBuffer := '';
|
|
FFeatureList.Delete(0);
|
|
|
|
i := 0;
|
|
while i < FFeatureList.Count do begin;
|
|
FFeatureList[i] := Copy(FFeatureList[i], 5, Length(FFeatureList[i])); // delete the response code crap
|
|
FFeatureList[i] := StringReplace(FFeatureList[i], '=', ' ', [rfReplaceAll]);
|
|
if FFeatureList.IndexOf(FFeatureList[i]) <> i then begin
|
|
FFeatureList.Delete(i);
|
|
Continue;
|
|
end;
|
|
Inc(i);
|
|
end;
|
|
end;
|
|
|
|
procedure TLSMTPClient.EvaluateAnswer(const Ans: string);
|
|
|
|
function GetNum: Integer;
|
|
begin
|
|
try
|
|
Result := StrToInt(Copy(Ans, 1, 3));
|
|
except
|
|
Result := -1;
|
|
end;
|
|
end;
|
|
|
|
function ValidResponse(const Answer: string): Boolean; inline;
|
|
begin
|
|
Result := (Length(Ans) >= 3) and
|
|
(Ans[1] in ['1'..'5']) and
|
|
(Ans[2] in ['0'..'9']) and
|
|
(Ans[3] in ['0'..'9']);
|
|
|
|
if Result then
|
|
Result := (Length(Ans) = 3) or ((Length(Ans) > 3) and (Ans[4] = ' '));
|
|
end;
|
|
|
|
procedure Eventize(const aStatus: TLSMTPStatus; const Res: Boolean);
|
|
begin
|
|
FStatus.Remove;
|
|
if Res then begin
|
|
if Assigned(FOnSuccess) and (aStatus in FStatusSet) then
|
|
FOnSuccess(FConnection.Iterator, aStatus);
|
|
end else begin
|
|
if Assigned(FOnFailure) and (aStatus in FStatusSet) then
|
|
FOnFailure(FConnection.Iterator, aStatus);
|
|
end;
|
|
end;
|
|
|
|
var
|
|
x: Integer;
|
|
begin
|
|
x := GetNum;
|
|
|
|
if ValidResponse(Ans) and not FStatus.Empty then
|
|
case FStatus.First.Status of
|
|
ssCon,
|
|
ssHelo,
|
|
ssEhlo: case x of
|
|
200..299: begin
|
|
case FStatus.First.Status of
|
|
ssCon : EvaluateServer;
|
|
ssEhlo : EvaluateFeatures;
|
|
end;
|
|
Eventize(FStatus.First.Status, True);
|
|
end;
|
|
else begin
|
|
Eventize(FStatus.First.Status, False);
|
|
Disconnect(False);
|
|
FFeatureList.Clear;
|
|
FTempBuffer := '';
|
|
end;
|
|
end;
|
|
|
|
ssStartTLS:
|
|
case x of
|
|
200..299: begin
|
|
Eventize(FStatus.First.Status, True);
|
|
FConnection.Iterator.SetState(ssSSLActive);
|
|
end;
|
|
else begin
|
|
Eventize(FStatus.First.Status, False);
|
|
end;
|
|
end;
|
|
|
|
ssAuthLogin:
|
|
case x of
|
|
200..299: begin
|
|
Eventize(FStatus.First.Status, True);
|
|
end;
|
|
300..399: if FAuthStep = 0 then begin
|
|
AddToBuffer(FStatus.First.Args[1] + CRLF);
|
|
Inc(FAuthStep);
|
|
SendData;
|
|
end else if FAuthStep = 1 then begin
|
|
AddToBuffer(FStatus.First.Args[2] + CRLF);
|
|
Inc(FAuthStep);
|
|
SendData;
|
|
end else begin
|
|
Eventize(FStatus.First.Status, False);
|
|
end;
|
|
else begin
|
|
Eventize(FStatus.First.Status, False);
|
|
end;
|
|
end;
|
|
|
|
ssAuthPlain:
|
|
case x of
|
|
200..299: begin
|
|
Eventize(FStatus.First.Status, True);
|
|
end;
|
|
300..399: begin
|
|
AddToBuffer(FStatus.First.Args[1] + FStatus.First.Args[2] + CRLF);
|
|
SendData;
|
|
end;
|
|
else begin
|
|
Eventize(FStatus.First.Status, False);
|
|
end;
|
|
end;
|
|
|
|
ssMail,
|
|
ssRcpt: begin
|
|
Eventize(FStatus.First.Status, (x >= 200) and (x < 299));
|
|
end;
|
|
|
|
ssData: case x of
|
|
200..299: begin
|
|
Eventize(FStatus.First.Status, True);
|
|
end;
|
|
300..399: begin
|
|
AddToBuffer(FDataBuffer);
|
|
FDataBuffer := '';
|
|
SendData(True);
|
|
end;
|
|
else begin
|
|
FDataBuffer := '';
|
|
Eventize(FStatus.First.Status, False);
|
|
end;
|
|
end;
|
|
|
|
ssRset: begin
|
|
Eventize(FStatus.First.Status, (x >= 200) and (x < 299));
|
|
end;
|
|
|
|
ssQuit: begin
|
|
Eventize(FStatus.First.Status, (x >= 200) and (x < 299));
|
|
{ if Assigned(FOnDisconnect) then
|
|
FOnDisconnect(FConnection.Iterator);}
|
|
Disconnect(False);
|
|
end;
|
|
end;
|
|
|
|
if FStatus.Empty and not FCommandFront.Empty then
|
|
ExecuteFrontCommand;
|
|
end;
|
|
|
|
procedure TLSMTPClient.ExecuteFrontCommand;
|
|
begin
|
|
with FCommandFront.First do
|
|
case Status of
|
|
ssHelo: Helo(Args[1]);
|
|
ssEhlo: Ehlo(Args[1]);
|
|
ssMail: Mail(Args[1]);
|
|
ssRcpt: Rcpt(Args[1]);
|
|
ssData: Data(Args[1]);
|
|
ssRset: Rset;
|
|
ssQuit: Quit;
|
|
end;
|
|
FCommandFront.Remove;
|
|
end;
|
|
|
|
procedure TLSMTPClient.AddToBuffer(s: string);
|
|
var
|
|
i: Integer;
|
|
Skip: Boolean = False;
|
|
begin
|
|
for i := 1 to Length(s) do begin
|
|
if Skip then begin
|
|
Skip := False;
|
|
Continue;
|
|
end;
|
|
|
|
if (s[i] = #13) or (s[i] = #10) then begin
|
|
if s[i] = #13 then
|
|
if (i < Length(s)) and (s[i + 1] = #10) then begin
|
|
FCharCount := 0;
|
|
Skip := True; // skip the crlf
|
|
end else begin // insert LF to a standalone CR
|
|
System.Insert(#10, s, i + 1);
|
|
FCharCount := 0;
|
|
Skip := True; // skip the new crlf
|
|
end;
|
|
|
|
if s[i] = #10 then begin
|
|
System.Insert(#13, s, i);
|
|
FCharCount := 0;
|
|
Skip := True; // skip the new crlf
|
|
end;
|
|
end else if FCharCount >= 1000 then begin // line too long
|
|
System.Insert(CRLF, s, i);
|
|
FCharCount := 0;
|
|
Skip := True;
|
|
end else
|
|
Inc(FCharCount);
|
|
end;
|
|
|
|
FBuffer := FBuffer + s;
|
|
end;
|
|
|
|
procedure TLSMTPClient.SendData(const FromStream: Boolean = False);
|
|
const
|
|
SBUF_SIZE = 65535;
|
|
|
|
procedure FillBuffer;
|
|
var
|
|
s: string;
|
|
begin
|
|
SetLength(s, SBUF_SIZE - Length(FBuffer));
|
|
SetLength(s, FStream.Read(s[1], Length(s)));
|
|
|
|
AddToBuffer(s);
|
|
|
|
if FStream.Position = FStream.Size then begin // we finished the stream
|
|
AddToBuffer(CRLF + '.' + CRLF);
|
|
FStream := nil;
|
|
end;
|
|
end;
|
|
|
|
var
|
|
n: Integer;
|
|
Sent: Integer;
|
|
begin
|
|
if FromStream and Assigned(FStream) then
|
|
FillBuffer;
|
|
|
|
n := 1;
|
|
Sent := 0;
|
|
while (Length(FBuffer) > 0) and (n > 0) do begin
|
|
n := FConnection.SendMessage(FBuffer);
|
|
Sent := Sent + n;
|
|
if n > 0 then
|
|
Delete(FBuffer, 1, n);
|
|
|
|
if FromStream and Assigned(FStream) and (Length(FBuffer) < SBUF_SIZE) then
|
|
FillBuffer;
|
|
end;
|
|
|
|
if Assigned(FOnSent) and (FStatus.First.Status = ssData) then
|
|
FOnSent(FConnection.Iterator, Sent);
|
|
end;
|
|
|
|
function TLSMTPClient.EncodeBase64(const s: string): string;
|
|
var
|
|
Dummy: TBogusStream;
|
|
Enc: TBase64EncodingStream;
|
|
begin
|
|
Result := '';
|
|
if Length(s) = 0 then
|
|
Exit;
|
|
|
|
Dummy := TBogusStream.Create;
|
|
Enc := TBase64EncodingStream.Create(Dummy);
|
|
|
|
Enc.Write(s[1], Length(s));
|
|
Enc.Free;
|
|
SetLength(Result, Dummy.Size);
|
|
Dummy.Read(Result[1], Dummy.Size);
|
|
|
|
Dummy.Free;
|
|
end;
|
|
|
|
function TLSMTPClient.Connect(const aHost: string; const aPort: Word = 25): Boolean;
|
|
begin
|
|
Result := False;
|
|
Disconnect(True);
|
|
if FConnection.Connect(aHost, aPort) then begin
|
|
FTempBuffer := '';
|
|
FHost := aHost;
|
|
FPort := aPort;
|
|
FStatus.Insert(MakeStatusRec(ssCon, '', ''));
|
|
Result := True;
|
|
end;
|
|
end;
|
|
|
|
function TLSMTPClient.Connect: Boolean;
|
|
begin
|
|
Result := Connect(FHost, FPort);
|
|
end;
|
|
|
|
function TLSMTPClient.Get(var aData; const aSize: Integer; aSocket: TLSocket): Integer;
|
|
var
|
|
s: string;
|
|
begin
|
|
Result := FConnection.Get(aData, aSize, aSocket);
|
|
if Result > 0 then begin
|
|
SetLength(s, Result);
|
|
Move(aData, PChar(s)^, Result);
|
|
CleanInput(s);
|
|
end;
|
|
end;
|
|
|
|
function TLSMTPClient.GetMessage(out msg: string; aSocket: TLSocket): Integer;
|
|
begin
|
|
Result := FConnection.GetMessage(msg, aSocket);
|
|
if Result > 0 then
|
|
Result := CleanInput(msg);
|
|
end;
|
|
|
|
procedure TLSMTPClient.SendMail(From, Recipients, Subject, Msg: string);
|
|
var
|
|
i: Integer;
|
|
begin
|
|
FStream := nil;
|
|
From := EncodeMimeHeaderText(From);
|
|
Recipients := EncodeMimeHeaderText(Recipients);
|
|
Subject := EncodeMimeHeaderText(Subject);
|
|
|
|
if (Length(Recipients) > 0) and (Length(From) > 0) then begin
|
|
Mail(From);
|
|
FSL.CommaText := StringReplace(Recipients, ' ', ',', [rfReplaceAll]);
|
|
for i := 0 to FSL.Count-1 do
|
|
Rcpt(FSL[i]);
|
|
Data('From: ' + From + CRLF + 'Subject: ' + Subject + CRLF + 'To: ' + FSL.CommaText + CRLF + CRLF + Msg);
|
|
end;
|
|
end;
|
|
|
|
procedure TLSMTPClient.SendMail(From, Recipients, Subject: string; aStream: TStream);
|
|
var
|
|
i: Integer;
|
|
begin
|
|
From := EncodeMimeHeaderText(From);
|
|
Recipients := EncodeMimeHeaderText(Recipients);
|
|
Subject := EncodeMimeHeaderText(Subject);
|
|
|
|
FStream := aStream;
|
|
|
|
if (Length(Recipients) > 0) and (Length(From) > 0) then begin
|
|
Mail(From);
|
|
FSL.CommaText := StringReplace(Recipients, ' ', ',', [rfReplaceAll]);
|
|
for i := 0 to FSL.Count-1 do
|
|
Rcpt(FSL[i]);
|
|
Data('From: ' + From + CRLF + 'Subject: ' + Subject + CRLF + 'To: ' + FSL.CommaText + CRLF);
|
|
end;
|
|
end;
|
|
|
|
procedure TLSMTPClient.SendMail(aMail: TMail);
|
|
begin
|
|
if Length(aMail.FMailText) > 0 then
|
|
SendMail(aMail.Sender, aMail.Recipients, aMail.Subject, aMail.FMailText)
|
|
else if Assigned(aMail.FMailStream) then
|
|
SendMail(aMail.Sender, aMail.Recipients, aMail.Subject, aMail.FMailStream);
|
|
end;
|
|
|
|
procedure TLSMTPClient.Helo(aHost: string = '');
|
|
begin
|
|
if Length(aHost) = 0 then
|
|
aHost := FHost;
|
|
|
|
if CanContinue(ssHelo, aHost, '') then begin
|
|
AddToBuffer('HELO ' + aHost + CRLF);
|
|
FStatus.Insert(MakeStatusRec(ssHelo, '', ''));
|
|
SendData;
|
|
end;
|
|
end;
|
|
|
|
procedure TLSMTPClient.Ehlo(aHost: string = '');
|
|
begin
|
|
if Length(aHost) = 0 then
|
|
aHost := FHost;
|
|
if CanContinue(ssEhlo, aHost, '') then begin
|
|
FTempBuffer := ''; // for ehlo response
|
|
AddToBuffer('EHLO ' + aHost + CRLF);
|
|
FStatus.Insert(MakeStatusRec(ssEhlo, '', ''));
|
|
SendData;
|
|
end;
|
|
end;
|
|
|
|
procedure TLSMTPClient.StartTLS;
|
|
begin
|
|
if CanContinue(ssStartTLS, '', '') then begin
|
|
AddToBuffer('STARTTLS' + CRLF);
|
|
FStatus.Insert(MakeStatusRec(ssStartTLS, '', ''));
|
|
SendData;
|
|
end;
|
|
end;
|
|
|
|
procedure TLSMTPClient.AuthLogin(aName, aPass: string);
|
|
begin
|
|
aName := EncodeBase64(aName);
|
|
aPass := EncodeBase64(aPass);
|
|
FAuthStep := 0; // first, send username
|
|
|
|
if CanContinue(ssAuthLogin, aName, aPass) then begin
|
|
AddToBuffer('AUTH LOGIN' + CRLF);
|
|
FStatus.Insert(MakeStatusRec(ssAuthLogin, aName, aPass));
|
|
SendData;
|
|
end;
|
|
end;
|
|
|
|
procedure TLSMTPClient.AuthPlain(aName, aPass: string);
|
|
begin
|
|
aName := EncodeBase64(#0 + aName);
|
|
aPass := EncodeBase64(#0 + aPass);
|
|
FAuthStep := 0;
|
|
|
|
if CanContinue(ssAuthPlain, aName, aPass) then begin
|
|
AddToBuffer('AUTH PLAIN' + CRLF);
|
|
FStatus.Insert(MakeStatusRec(ssAuthPlain, aName, aPass));
|
|
SendData;
|
|
end;
|
|
end;
|
|
|
|
procedure TLSMTPClient.Mail(const From: string);
|
|
begin
|
|
if CanContinue(ssMail, From, '') then begin
|
|
AddToBuffer('MAIL FROM:' + '<' + From + '>' + CRLF);
|
|
FStatus.Insert(MakeStatusRec(ssMail, '', ''));
|
|
SendData;
|
|
end;
|
|
end;
|
|
|
|
procedure TLSMTPClient.Rcpt(const RcptTo: string);
|
|
begin
|
|
if CanContinue(ssRcpt, RcptTo, '') then begin
|
|
AddToBuffer('RCPT TO:' + '<' + RcptTo + '>' + CRLF);
|
|
FStatus.Insert(MakeStatusRec(ssRcpt, '', ''));
|
|
SendData;
|
|
end;
|
|
end;
|
|
|
|
procedure TLSMTPClient.Data(const Msg: string);
|
|
begin
|
|
if CanContinue(ssData, Msg, '') then begin
|
|
AddToBuffer('DATA ' + CRLF);
|
|
FDataBuffer := '';
|
|
|
|
if Assigned(FStream) then begin
|
|
if Length(Msg) > 0 then
|
|
FDataBuffer := Msg;
|
|
end else
|
|
FDataBuffer := Msg + CRLF + '.' + CRLF;
|
|
|
|
FStatus.Insert(MakeStatusRec(ssData, '', ''));
|
|
SendData(False);
|
|
end;
|
|
end;
|
|
|
|
procedure TLSMTPClient.Rset;
|
|
begin
|
|
if CanContinue(ssRset, '', '') then begin
|
|
AddToBuffer('RSET' + CRLF);
|
|
FStatus.Insert(MakeStatusRec(ssRset, '', ''));
|
|
SendData;
|
|
end;
|
|
end;
|
|
|
|
procedure TLSMTPClient.Quit;
|
|
begin
|
|
if CanContinue(ssQuit, '', '') then begin
|
|
AddToBuffer('QUIT' + CRLF);
|
|
FStatus.Insert(MakeStatusRec(ssQuit, '', ''));
|
|
SendData;
|
|
end;
|
|
end;
|
|
|
|
procedure TLSMTPClient.Disconnect(const Forced: Boolean = True);
|
|
begin
|
|
FConnection.Disconnect(Forced);
|
|
FStatus.Clear;
|
|
FCommandFront.Clear;
|
|
end;
|
|
|
|
procedure TLSMTPClient.CallAction;
|
|
begin
|
|
FConnection.CallAction;
|
|
end;
|
|
|
|
{ TMail }
|
|
|
|
function TMail.GetCount: Integer;
|
|
begin
|
|
Result := FMailStream.Count;
|
|
end;
|
|
|
|
function TMail.GetSection(i: Integer): TMimeSection;
|
|
begin
|
|
Result := FMailStream.Sections[i];
|
|
end;
|
|
|
|
procedure TMail.SetSection(i: Integer; const AValue: TMimeSection);
|
|
begin
|
|
FMailStream.Sections[i] := aValue;
|
|
end;
|
|
|
|
constructor TMail.Create;
|
|
begin
|
|
FMailStream := TMimeStream.Create;
|
|
end;
|
|
|
|
destructor TMail.Destroy;
|
|
begin
|
|
FMailStream.Free;
|
|
end;
|
|
|
|
procedure TMail.AddTextSection(const aText: string; const aCharSet: string);
|
|
begin
|
|
FMailStream.AddTextSection(aText, aCharSet);
|
|
end;
|
|
|
|
procedure TMail.AddFileSection(const aFileName: string);
|
|
begin
|
|
FMailStream.AddFileSection(aFileName);
|
|
end;
|
|
|
|
procedure TMail.AddStreamSection(aStream: TStream; const FreeStream: Boolean);
|
|
begin
|
|
FMailStream.AddStreamSection(aStream, FreeStream);
|
|
end;
|
|
|
|
procedure TMail.DeleteSection(const i: Integer);
|
|
begin
|
|
FMailStream.Delete(i);
|
|
end;
|
|
|
|
procedure TMail.RemoveSection(aSection: TMimeSection);
|
|
begin
|
|
FMailStream.Remove(aSection);
|
|
end;
|
|
|
|
procedure TMail.Reset;
|
|
begin
|
|
FMailStream.Reset;
|
|
end;
|
|
|
|
|
|
end.
|
|
|