fpc/utils/fppkg/lnet/lsmtp.pp
Almindor 0dce152199 * update lnet in fppkg to 0.5.8
git-svn-id: trunk@9012 -
2007-10-31 09:08:27 +00:00

755 lines
20 KiB
ObjectPascal

{ lNet SMTP unit
CopyRight (C) 2005-2007 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, 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, lNet, lEvents, lCommon, lMimeWrapper, lMimeStreams;
type
TLSMTP = class;
TLSMTPClient = class;
TLSMTPStatus = (ssNone, ssCon, ssHelo, ssEhlo, ssMail,
ssRcpt, ssData, ssRset, ssQuit);
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);
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;
protected
function GetTimeout: Integer;
procedure SetTimeout(const AValue: Integer);
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;
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;
end;
{ TLSMTPClient }
TLSMTPClient = class(TLSMTP, ILClient)
protected
FStatus: TLSMTPStatusFront;
FCommandFront: TLSMTPStatusFront;
FPipeLine: Boolean;
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
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 EvaluateAnswer(const Ans: string);
procedure ExecuteFrontCommand;
procedure ClearCR_LF;
procedure SendData(const FromStream: Boolean = False);
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 Mail(const From: string);
procedure Rcpt(const RcptTo: string);
procedure Data(const Msg: string);
procedure Rset;
procedure Quit;
procedure Disconnect; 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..ssQuit] of string = ('ssNone', 'ssCon', 'ssHelo', 'ssEhlo', 'ssMail',
'ssRcpt', 'ssData', 'ssRset', 'ssQuit');
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.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);
FConnection := TLTcp.Create(nil);
end;
destructor TLSMTP.Destroy;
begin
FConnection.Free;
inherited Destroy;
end;
{ TLSMTPClient }
constructor TLSMTPClient.Create(aOwner: TComponent);
begin
inherited Create(aOwner);
FPort := 25;
FStatusSet := []; // empty set for "ok/not-ok" Event
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
Quit;
FSL.Free;
FStatus.Free;
FCommandFront.Free;
inherited Destroy;
end;
procedure TLSMTPClient.OnEr(const msg: string; aSocket: TLSocket);
begin
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;
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.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
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
Eventize(FStatus.First.Status, True);
FStatus.Remove;
end;
else begin
Eventize(FStatus.First.Status, False);
Disconnect;
end;
end;
ssMail,
ssRcpt: begin
Eventize(FStatus.First.Status, (x >= 200) and (x < 299));
FStatus.Remove;
end;
ssData: case x of
200..299: begin
Eventize(FStatus.First.Status, True);
FStatus.Remove;
end;
300..399: begin
FBuffer := FDataBuffer;
FDataBuffer := '';
SendData(True);
end;
else begin
FDataBuffer := '';
Eventize(FStatus.First.Status, False);
FStatus.Remove;
end;
end;
ssRset: begin
Eventize(FStatus.First.Status, (x >= 200) and (x < 299));
FStatus.Remove;
end;
ssQuit: begin
Eventize(FStatus.First.Status, (x >= 200) and (x < 299));
FStatus.Remove;
if Assigned(FOnDisconnect) then
FOnDisconnect(FConnection.Iterator);
Disconnect;
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.ClearCR_LF;
var
i: Integer;
Skip: Boolean = False;
begin
for i := 1 to Length(FBuffer) do begin
if Skip then begin
Skip := False;
Continue;
end;
if (FBuffer[i] = #13) or (FBuffer[i] = #10) then begin
if FBuffer[i] = #13 then
if (i < Length(FBuffer)) and (FBuffer[i + 1] = #10) then begin
FCharCount := 0;
Skip := True; // skip the crlf
end else begin // insert LF to a standalone CR
System.Insert(#10, FBuffer, i + 1);
FCharCount := 0;
Skip := True; // skip the new crlf
end;
if FBuffer[i] = #10 then begin
System.Insert(#13, FBuffer, i);
FCharCount := 0;
Skip := True; // skip the new crlf
end;
end else if FCharCount >= 1000 then begin // line too long
System.Insert(CRLF, FBuffer, i);
FCharCount := 0;
Skip := True;
end else
Inc(FCharCount);
end;
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)));
FBuffer := FBuffer + s;
if FStream.Position = FStream.Size then begin // we finished the stream
FBuffer := FBuffer + 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
ClearCR_LF;
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.Connect(const aHost: string; const aPort: Word = 25): Boolean;
begin
Result := False;
Disconnect;
if FConnection.Connect(aHost, aPort) then begin
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 + Msg);
Rset;
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);
Rset;
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(Host) = 0 then
aHost := FHost;
if CanContinue(ssHelo, aHost, '') then begin
FBuffer := FBuffer + '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
FBuffer := FBuffer + 'EHLO ' + aHost + CRLF;
FStatus.Insert(MakeStatusRec(ssEhlo, '', ''));
SendData;
end;
end;
procedure TLSMTPClient.Mail(const From: string);
begin
if CanContinue(ssMail, From, '') then begin
FBuffer := FBuffer + 'MAIL FROM:' + '<' + From + '>' + CRLF;
FStatus.Insert(MakeStatusRec(ssMail, '', ''));
SendData;
end;
end;
procedure TLSMTPClient.Rcpt(const RcptTo: string);
begin
if CanContinue(ssRcpt, RcptTo, '') then begin
FBuffer := FBuffer + 'RCPT TO:' + '<' + RcptTo + '>' + CRLF;
FStatus.Insert(MakeStatusRec(ssRcpt, '', ''));
SendData;
end;
end;
procedure TLSMTPClient.Data(const Msg: string);
begin
if CanContinue(ssData, Msg, '') then begin
FBuffer := '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
FBuffer := FBuffer + 'RSET' + CRLF;
FStatus.Insert(MakeStatusRec(ssRset, '', ''));
SendData;
end;
end;
procedure TLSMTPClient.Quit;
begin
if CanContinue(ssQuit, '', '') then begin
FBuffer := FBuffer + 'QUIT' + CRLF;
FStatus.Insert(MakeStatusRec(ssQuit, '', ''));
SendData;
end;
end;
procedure TLSMTPClient.Disconnect;
begin
FConnection.Disconnect;
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;
end.