restemplate/indy/Protocols/IdNNTP.pas

1501 lines
51 KiB
Plaintext

{
$Project$
$Workfile$
$Revision$
$DateUTC$
$Id$
This file is part of the Indy (Internet Direct) project, and is offered
under the dual-licensing agreement described on the Indy website.
(http://www.indyproject.org/)
Copyright:
(c) 1993-2005, Chad Z. Hower and the Indy Pit Crew. All rights reserved.
}
{
$Log$
}
{
Rev 1.29 3/1/2005 3:35:48 PM BGooijen
Auth
Rev 1.28 1/11/2005 3:09:06 AM JPMugaas
Fix. A NNTP banner should not be obtained after STARTTLS succeded.
Rev 1.27 10/26/2004 10:33:46 PM JPMugaas
Updated refs.
Rev 1.26 2004.05.20 11:37:02 AM czhower
IdStreamVCL
Rev 1.25 16/05/2004 14:30:42 CCostelloe
ReceiveHeader checks added in case message has no body
Rev 1.24 3/7/2004 11:21:50 PM JPMugaas
Compiler warnings.
Rev 1.23 2004.03.06 1:31:46 PM czhower
To match Disconnect changes to core.
Rev 1.22 2004.02.03 5:44:10 PM czhower
Name changes
Rev 1.21 2004.01.28 9:36:32 PM czhower
Fixed search and replace error
Rev 1.20 2004.01.27 1:13:36 PM czhower
T --> TId
var --> out
Rev 1.19 1/26/2004 1:16:46 PM JPMugaas
SSL Reenabled.
Rev 1.18 2004.01.22 9:28:44 PM czhower
DotNetExclude for TLS.
Rev 1.17 1/21/2004 3:26:50 PM JPMugaas
InitComponent
Rev 1.16 1/5/2004 8:22:18 PM JMJacobson
Updated TIdNNTP.GetCapability to handle empty LIST EXTENSIONS response
(response 215)
Rev 1.15 11/11/03 11:06:18 AM RLebeau
Updated SendCmd() to test for a 281 response when issuing an AUTHINFO USER
command, as per RFC 2980
Rev 1.14 2003.10.24 10:33:22 AM czhower
Saved first this time.
Rev 1.12 10/19/2003 5:31:52 PM DSiders
Added localization comments.
Rev 1.11 2003.10.14 9:57:16 PM czhower
Compile todos
Rev 1.10 2003.10.12 4:04:00 PM czhower
compile todos
Rev 1.9 9/10/2003 03:26:12 AM JPMugaas
Updated GetArticle(), GetBody(), and GetHeader() to use new
EnsureMsgIDBrackets() function in IdGlobal. Checked in on behalf of Remy
Lebeau
Rev 1.8 6/9/2003 05:14:58 AM JPMugaas
Fixed crical error.
Supports HDR and OVER commands defined in
http://www.ietf.org/internet-drafts/draft-ietf-nntpext-base-18.txt if feature
negotiation indicates that they are supported.
Added XHDR data parsing routine.
Added events for when we receive a line of data with XOVER or XHDR as per
John Jacobson's request.
Rev 1.7 6/9/2003 01:09:40 AM JPMugaas
Host wasn't published when it should have been published.
Rev 1.6 6/5/2003 04:54:00 AM JPMugaas
Reworkings and minor changes for new Reply exception framework.
Rev 1.5 5/8/2003 11:28:06 AM JPMugaas
Moved feature negoation properties down to the ExplicitTLSClient level as
feature negotiation goes hand in hand with explicit TLS support.
Rev 1.4 4/5/2003 02:06:20 PM JPMugaas
TLS handshake itself can now be handled.
Rev 1.3 3/27/2003 05:46:36 AM JPMugaas
Updated framework with an event if the TLS negotiation command fails.
Cleaned up some duplicate code in the clients.
Rev 1.2 3/26/2003 04:18:22 PM JPMugaas
Now supports implicit and explicit TLS.
Rev 1.1 2/24/2003 09:25:16 PM JPMugaas
Rev 1.0 11/13/2002 07:57:52 AM JPMugaas
2001-Dec - Chad Z. Hower a.k.a. Kudzu
-Continued modifications
2001-Oct - Chad Z. Hower a.k.a. Kudzu
-Massive reworking to fit the Indy 9 model and update a lot of outdated code
that was left over from Delphi 4 days. Updates now use overloaded functions.
There were also several problems with message number accounting.
2000-Jun-23 J. Peter Mugaas
-GetNewGroupsList, GetNewGroupsList, and GetNewNewsList No longer require
an Event handler if you provide a TStrings to those procedures
-ParseXOVER was added so that you could parse XOVER data
-ParseNewsGroup was ripped from GetNewGroupsList so that newsgroups can
be parsed while not downloading newsgroups
-Moved some duplicate code into a separate procedure
-The IdNNTP now uses the Indy exceptions and IdResourceStrings to facilitate
internationalization
2000-Apr-28 Mark L. Holmes
-Ported to Indy
2000-Apr-28
-Final Version
1999-Dec-29 MTL
-Moved to new Palette Scheme (Winshoes Servers)
}
unit IdNNTP;
interface
{$i IdCompilerDefines.inc}
uses
Classes,
IdAssignedNumbers, IdExplicitTLSClientServerBase, IdException, IdGlobal,
IdMessage, IdMessageClient, IdReplyRFC,
IdTCPServer, IdTCPConnection;
{
Original Author: Chad Z. Hower a.k.a. Kudzu
Amended and modified by: AHeid, Mark Holmes
}
type
// Most users of this component should use "mtReader"
TIdModeType = (mtStream, mtIHAVE, mtReader);
TIdNNTPPermission = (crCanPost, crNoPost, crAuthRequired, crTempUnavailable);
TIdModeSetResult = (mrCanStream, mrNoStream, mrCanIHAVE, mrNoIHAVE, mrCanPost, mrNoPost);
TIdEventStreaming = procedure (AMesgID: string; var AAccepted: Boolean)of object;
TIdNewsTransporTIdEvent = procedure (AMsg: TStrings) of object;
//AMsg can be an index number or a message ID depending upon the parameters of XHDR
TIdEvenTIdNewsgroupList = procedure(ANewsgroup: string; ALow, AHigh: Int64;
AType: string; var ACanContinue: Boolean) of object;
TIdEventXOVER = procedure(AArticleIndex : Int64; ASubject,
AFrom : String; ADate : TDateTime; AMsgId, AReferences : String; AByteCount,
ALineCount : Integer; AExtraData : String; var VCanContinue : Boolean) of object;
TIdEventNewNewsList = procedure(AMsgID: string; var ACanContinue: Boolean) of object;
TIdEventXHDREntry = procedure(AHeader : String; AMsg, AHeaderData : String; var ACanContinue: Boolean) of object;
//TODO: Add a TranslateRFC822 Marker - probably need to do it in TCPConnection and modify Capture
// Better yet, make capture an object
TIdNNTP = class(TIdMessageClient)
protected
FGreetingCode : Integer;
FMsgHigh: Int64;
FMsgLow: Int64;
FMsgCount: Int64;
FNewsAgent: string;
FOnNewsgroupList,
FOnNewGroupsList: TIdEvenTIdNewsgroupList;
FOnNewNewsList: TIdEventNewNewsList;
FOnXHDREntry : TIdEventXHDREntry;
FOnXOVER : TIdEventXOVER;
FModeType: TIdModeType;
FModeResult: TIdModeSetResult;
FPermission: TIdNNTPPermission;
FForceAuth: boolean;
FHDRSupported : Boolean;
FOVERSupported : Boolean;
//
procedure AfterConnect;
procedure GetCapability;
function ConvertDateTimeDist(ADate: TDateTime; AGMT: boolean;
ADistributions: string): string;
function GetSupportsTLS : boolean; override;
procedure InitComponent; override;
procedure ProcessGroupList(ACmd: string; AResponse: integer;
ALisTIdEvent: TIdEvenTIdNewsgroupList);
procedure XHDRCommon(AHeader, AParam : String);
procedure XOVERCommon(AParam : String);
procedure StartTLS;
public
procedure Check(AMsgIDs: TStrings; AResponses: TStrings);
procedure Connect; override;
destructor Destroy; override;
procedure DisconnectNotifyPeer; override;
function GetArticle(AMsg: TIdMessage): Boolean; overload;
function GetArticle(AMsgNo: Int64; AMsg: TIdMessage): Boolean; overload;
function GetArticle(AMsgID: string; AMsg: TIdMessage): Boolean; overload;
function GetArticle(AMsg: TStrings): Boolean; overload;
function GetArticle(AMsgNo: Int64; AMsg: TStrings): Boolean; overload;
function GetArticle(AMsgID: string; AMsg: TStrings): Boolean; overload;
function GetArticle(AMsg: TStream): Boolean; overload;
function GetArticle(AMsgNo: Int64; AMsg: TStream): Boolean; overload;
function GetArticle(AMsgID: string; AMsg: TStream): Boolean; overload;
function GetBody(AMsg: TIdMessage): Boolean; overload;
function GetBody(AMsgNo: Int64; AMsg: TIdMessage): Boolean; overload;
function GetBody(AMsgID: string; AMsg: TIdMessage): Boolean; overload;
function GetBody(AMsg: TStrings): Boolean; overload;
function GetBody(AMsgNo: Int64; AMsg: TStrings): Boolean; overload;
function GetBody(AMsgID: string; AMsg: TStrings): Boolean; overload;
function GetBody(AMsg: TStream): Boolean; overload;
function GetBody(AMsgNo: Int64; AMsg: TStream): Boolean; overload;
function GetBody(AMsgID: string; AMsg: TStream): Boolean; overload;
function GetHeader(AMsg: TIdMessage): Boolean; overload;
function GetHeader(AMsgNo: Int64; AMsg: TIdMessage): Boolean; overload;
function GetHeader(AMsgID: string; AMsg: TIdMessage): Boolean; overload;
function GetHeader(AMsg: TStrings): Boolean; overload;
function GetHeader(AMsgNo: Int64; AMsg: TStrings): Boolean; overload;
function GetHeader(AMsgID: string; AMsg: TStrings): Boolean; overload;
function GetHeader(AMsg: TStream): Boolean; overload;
function GetHeader(AMsgNo: Int64; AMsg: TStream): Boolean; overload;
function GetHeader(AMsgID: string; AMsg: TStream): Boolean; overload;
procedure GetNewsgroupList; overload;
procedure GetNewsgroupList(AList: TStrings); overload;
procedure GetNewsgroupList(AStream: TStream); overload;
procedure GetNewGroupsList(ADate: TDateTime; AGMT: boolean;
ADistributions: string); overload;
procedure GetNewGroupsList(ADate: TDateTime; AGMT: boolean;
ADistributions: string; AList : TStrings); overload;
procedure GetNewNewsList(ANewsgroups: string;
ADate: TDateTime; AGMT: boolean; ADistributions: string); overload;
procedure GetNewNewsList(ANewsgroups: string; ADate: TDateTime;
AGMT: boolean; ADistributions: string; AList : TStrings); overload;
procedure GetOverviewFMT(AResponse: TStrings);
function IsExtCmdSupported(AExtension : String) : Boolean;
procedure IHAVE(AMsg: TStrings);
function Next: Boolean;
function Previous: Boolean;
procedure ParseXOVER(Aline: String; var AArticleIndex : Int64; var ASubject,
AFrom : String; var ADate : TDateTime; var AMsgId, AReferences : String; var AByteCount,
ALineCount : Integer; var AExtraData : String);
procedure ParseNewsGroup(ALine : String; out ANewsGroup: string; out AHi, ALo : Int64;
out AStatus : String);
procedure ParseXHDRLine(ALine : String; out AMsg : String; out AHeaderData : String);
procedure Post(AMsg: TIdMessage); overload;
procedure Post(AStream: TStream); overload;
function SendCmd(AOut: string; const AResponse: array of Int16;
AEncoding: IIdTextEncoding = nil): Int16; override;
function SelectArticle(AMsgNo: Int64): Boolean;
procedure SelectGroup(AGroup: string);
function TakeThis(AMsgID: string; AMsg: TStream): string;
procedure XHDR(AHeader: string; AParam: string; AResponse: TStrings); overload;
procedure XHDR(AHeader: string; AParam: string); overload;
procedure XOVER(AParam: string; AResponse: TStrings); overload;
procedure XOVER(AParam: string; AResponse: TStream); overload;
procedure XOVER(AParam: string); overload;
procedure SendAuth;
//
property ModeResult: TIdModeSetResult read FModeResult write FModeResult;
property MsgCount: Int64 read FMsgCount;
property MsgHigh: Int64 read FMsgHigh;
property MsgLow: Int64 read FMsgLow;
property Permission: TIdNNTPPermission read FPermission;
published
property NewsAgent: string read FNewsAgent write FNewsAgent;
property Mode: TIdModeType read FModeType write FModeType default mtReader;
property Password;
property Username;
property OnNewsgroupList: TIdEvenTIdNewsgroupList read FOnNewsgroupList write FOnNewsgroupList;
property OnNewGroupsList: TIdEvenTIdNewsGroupList read FOnNewGroupsList write FOnNewGroupsList;
property OnNewNewsList: TIdEventNewNewsList read FOnNewNewsList write FOnNewNewsList;
property OnXHDREntry : TIdEventXHDREntry read FOnXHDREntry write FOnXHDREntry;
property OnXOVER : TIdEventXOVER read FOnXOVER write FOnXOVER;
property OnTLSNotAvailable;
property Port default IdPORT_NNTP;
property Host;
property UseTLS;
property ForceAuth:boolean read FForceAuth write FForceAuth default false;
end;
EIdNNTPException = class(EIdException);
EIdNNTPNoOnNewGroupsList = class(EIdNNTPException);
EIdNNTPNoOnNewNewsList = class(EIdNNTPException);
EIdNNTPNoOnNewsgroupList = class(EIdNNTPException);
EIdNNTPNoOnXHDREntry = class(EIdNNTPException);
EIdNNTPNoOnXOVER = class(EIdNNTPException);
EIdNNTPStringListNotInitialized = class(EIdNNTPException);
EIdNNTPConnectionRefused = class (EIdReplyRFCError);
implementation
uses
IdComponent,
IdGlobalProtocols,
IdResourceStringsProtocols,
IdSSL, SysUtils;
procedure TIdNNTP.ParseXOVER(Aline : String;
var AArticleIndex : Int64;
var ASubject,
AFrom : String;
var ADate : TDateTime;
var AMsgId,
AReferences : String;
var AByteCount,
ALineCount : Integer;
var AExtraData : String);
begin
{Strip backspace and tab junk sequences which occur after a tab separator so they don't throw off any code}
ALine := ReplaceAll(ALine, #9#8#9, #9);
{Article Index}
AArticleIndex := IndyStrToInt64(Fetch(ALine, #9), 0);
{Subject}
ASubject := Fetch(ALine, #9);
{From}
AFrom := Fetch(ALine, #9);
{Date}
ADate := GMTToLocalDateTime(Fetch(Aline, #9));
{Message ID}
AMsgId := Fetch(Aline, #9);
{References}
AReferences := Fetch(ALine, #9);
{Byte Count}
AByteCount := IndyStrToInt(Fetch(ALine, #9), 0);
{Line Count}
ALineCount := IndyStrToInt(Fetch(ALine, #9), 0);
{Extra data}
AExtraData := ALine;
end;
procedure TIdNNTP.ParseNewsGroup(ALine : String; out ANewsGroup : String;
out AHi, ALo : Int64; out AStatus : String);
begin
ANewsgroup := Fetch(ALine, ' ');
AHi := IndyStrToInt64(Fetch(Aline, ' '), 0);
ALo := IndyStrToInt64(Fetch(ALine, ' '), 0);
AStatus := ALine;
end;
procedure TIdNNTP.InitComponent;
begin
inherited InitComponent;
Mode := mtReader;
Port := IdPORT_NNTP;
ForceAuth := false;
FRegularProtPort := IdPORT_NNTP;
FImplicitTLSProtPort := IdPORT_SNEWS;
end;
function TIdNNTP.SendCmd(AOut: string; const AResponse: Array of Int16;
AEncoding: IIdTextEncoding = nil): Int16;
begin
// NOTE: Responses must be passed as arrays so that the proper inherited SendCmd is called
// and a stack overflow is not caused.
Result := inherited SendCmd(AOut, [], AEncoding);
if (Result = 480) or (Result = 450) then
begin
SendAuth;
Result := inherited SendCmd(AOut, AResponse, AEncoding);
end else begin
CheckResponse(Result, AResponse);
end;
end;
procedure TIdNNTP.Connect;
begin
inherited Connect;
try
FGreetingCode := GetResponse;
AfterConnect;
StartTLS;
if ForceAuth then begin
SendAuth;
end;
except
Disconnect(False);
raise;
end;
end;
{ This procedure gets the overview format as suported by the server }
procedure TIdNNTP.GetOverviewFMT(AResponse: TStrings);
var
LEncoding: IIdTextEncoding;
begin
SendCmd('LIST OVERVIEW.FMT', 215); {do not localize}
LEncoding := IndyTextEncoding_8Bit;
IOHandler.Capture(AResponse, LEncoding{$IFDEF STRING_IS_ANSI}, LEncoding{$ENDIF});
end;
{ Send the XOVER Command. XOVER [Range]
Range can be of the form: Article Number i.e. 1
Article Number followed by a dash
Article Number followed by a dash and aother number
Remember to select a group first and to issue a GetOverviewFMT so that you
can interpret the information sent by the server corectly. }
procedure TIdNNTP.XOVER(AParam: string; AResponse: TStrings);
var
LEncoding: IIdTextEncoding;
begin
XOVERCommon(AParam);
LEncoding := IndyTextEncoding_8Bit;
IOHandler.Capture(AResponse, LEncoding{$IFDEF STRING_IS_ANSI}, LEncoding{$ENDIF});
end;
procedure TIdNNTP.XOVER(AParam: string; AResponse: TStream);
var
LEncoding: IIdTextEncoding;
begin
XOVERCommon(AParam);
LEncoding := IndyTextEncoding_8Bit;
IOHandler.Capture(AResponse, LEncoding{$IFDEF STRING_IS_ANSI}, LEncoding{$ENDIF});
end;
{ Send the XHDR Command. XHDR Header (Range | Message-ID)
Range can be of the form: Article Number i.e. 1
Article Number followed by a dash
Article Number followed by a dash and aother number
Parm is either the Range or the MessageID of the articles you want. They
are Mutually Exclusive}
procedure TIdNNTP.XHDR(AHeader: string; AParam: String; AResponse: TStrings);
var
LEncoding: IIdTextEncoding;
begin
{ This method will send the XHDR command.
The programmer is responsible for choosing the correct header. Headers
that should always work as per RFC 1036 are:
From
Date
Newsgroups
Subject
Message-ID
Path
These Headers may work... They are optional per RFC1036 and new headers can
be added at any time as server implementation changes
Reply-To
Sender
Followup-To
Expires
References
Control
Distribution
Organization
Keywords
Summary
Approved
Lines
Xref
}
XHDRCommon(AHeader,AParam);
LEncoding := IndyTextEncoding_8Bit;
IOHandler.Capture(AResponse, LEncoding{$IFDEF STRING_IS_ANSI}, LEncoding{$ENDIF});
end;
procedure TIdNNTP.SelectGroup(AGroup: string);
var
s: string;
begin
SendCmd('GROUP ' + AGroup, [211]); {do not localize}
s := LastCmdResult.Text[0];
FMsgCount := IndyStrToInt64(Fetch(s), 0);
FMsgLow := IndyStrToInt64(Fetch(s), 0);
FMsgHigh := IndyStrToInt64(Fetch(s), 0);
end;
{ This method will send messages via the IHAVE command.
The IHAVE command first sends the message ID and waits for a response from the
server prior to sending the header and body. This command is of no practical
use for NNTP client readers as readers are generally denied the privelege
to execute the IHAVE command. this is a news transport command. So use this
when you are implementing a NNTP server send unit }
procedure TIdNNTP.IHAVE(AMsg: TStrings);
var
i : Integer;
MsgID : string;
begin
//TODO: Im not sure this fucntion works properly - needs checked
// Why is it not using a TIdMessage?
// Since we are merely forwarding messages we have already received
// it is assumed that the required header fields and body are already in place
// We need to get the message ID from the stringlist because it's required
// that we send it s part of the IHAVE command
for i := 0 to AMsg.Count - 1 do
begin
if IndyPos('Message-ID', AMsg.Strings[i]) > 0 then begin {do not localize}
MsgID := AMsg.Strings[i];
Fetch(MsgID,':');
Break;
end;
end;
SendCmd('IHAVE ' + MsgID, 335); {do not localize}
WriteRFCStrings(AMsg);
// Why is the response ignored? What is it?
Readln;
end;
(*
1.1.1 The CHECK command
CHECK <message-id>
CHECK is used by a peer to discover if the article with the specified
message-id should be sent to the server using the TAKETHIS command.
The peer does not have to wait for a response from the server before
sending the next command.
From using the responses to the sequence of CHECK commands, a list of
articles to be sent can be constructed for subsequent use by the
TAKETHIS command.
The use of the CHECK command for streaming is optional. Some
implementations will directly use the TAKETHIS command and send all
articles in the send queue on that peer for the server.
On some implementations, the use of the CHECK command is not
permitted when the server is in slave mode (via the SLAVE command).
Responses that are of the form X3X must specify the message-id in the
response.
1.1.2. Responses
238 no such article found, please send it to me
400 not accepting articles
431 try sending it again later
438 already have it, please don't send it to me
480 Transfer permission denied
500 Command not understood
*)
procedure TIdNNTP.Check(AMsgIDs: TStrings; AResponses: TStrings);
var
i: Integer;
begin
if not Assigned(AResponses) then begin
raise EIdNNTPStringListNotInitialized.Create(RSNNTPStringListNotInitialized);
end;
for i := 0 to AMsgIDs.Count - 1 do begin
IOHandler.WriteLn('CHECK '+ AMsgIDs.Strings[i]); {do not localize}
end;
for i := 0 to AMsgIDs.Count - 1 do begin
AResponses.Add(IOHandler.ReadLn)
end;
end;
(*
1.3.1 The TAKETHIS command
TAKETHIS <message-id>
TAKETHIS is used to send articles to a server when in streaming mode.
The entire article (header and body, in that sequence) is sent
immediately after the peer sends the TAKETHIS command. The peer does
not have to wait for a response from the server before sending the
next command and the associated article.
During transmission of the article, the peer should send the entire
article, including header and body, in the manner specified for text
transmission from the server. See RFC 977, Section 2.4.1 for
details.
Responses that are of the form X3X must specify the message-id in the
response.
1.3.2. Responses
239 article transferred ok
400 not accepting articles
439 article transfer failed
480 Transfer permission denied
500 Command not understood
*)
function TIdNNTP.TakeThis(AMsgID: string; AMsg: TStream): string;
// This message assumes AMsg is "raw" and has already taken care of . to ..
begin
SendCmd('TAKETHIS ' + AMsgID, 239); {do not localize}
IOHandler.Write(AMsg);
IOHandler.WriteLn('.');
end;
(*
3.10. The POST command
3.10.1. POST
POST
If posting is allowed, response code 340 is returned to indicate that
the article to be posted should be sent. Response code 440 indicates
that posting is prohibited for some installation-dependent reason.
If posting is permitted, the article should be presented in the
format specified by RFC850, and should include all required header
lines. After the article's header and body have been completely sent
by the client to the server, a further response code will be returned
to indicate success or failure of the posting attempt.
The text forming the header and body of the message to be posted
should be sent by the client using the conventions for text received
from the news server: A single period (".") on a line indicates the
end of the text, with lines starting with a period in the original
text having that period doubled during transmission.
No attempt shall be made by the server to filter characters, fold or
limit lines, or otherwise process incoming text. It is our intent
that the server just pass the incoming message to be posted to the
server installation's news posting software, which is separate from
this specification. See RFC850 for more details.
Since most installations will want the client news program to allow
the user to prepare his message using some sort of text editor, and
transmit it to the server for posting only after it is composed, the
client program should take note of the herald message that greeted it
when the connection was first established. This message indicates
whether postings from that client are permitted or not, and can be
used to caution the user that his access is read-only if that is the
case. This will prevent the user from wasting a good deal of time
composing a message only to find posting of the message was denied.
The method and determination of which clients and hosts may post is
installation dependent and is not covered by this specification.
3.10.2. Responses
240 article posted ok
340 send article to be posted. End with <CR-LF>.<CR-LF>
440 posting not allowed
441 posting failed
(for reference, one of the following codes will be sent upon initial
connection; the client program should determine whether posting is
generally permitted from these:) 200 server ready - posting allowed
201 server ready - no posting allowed
*)
procedure TIdNNTP.Post(AMsg: TIdMessage);
begin
SendCmd('POST', 340); {do not localize}
//Header
if Length(NewsAgent) > 0 then begin
AMsg.ExtraHeaders.Values['X-Newsreader'] := NewsAgent; {do not localize}
end;
SendMsg(AMsg);
SendCmd('.', 240);
end;
procedure TIdNNTP.Post(AStream: TStream);
begin
SendCmd('POST', 340); {do not localize}
IOHandler.Write(AStream);
SendCmd('.', 240);
end;
procedure TIdNNTP.ProcessGroupList(ACmd: string; AResponse: integer;
ALisTIdEvent: TIdEvenTIdNewsgroupList);
var
s1, sNewsgroup: string;
lLo, lHi: Int64;
sStatus: string;
LCanContinue: Boolean;
begin
BeginWork(wmRead, 0); try
SendCmd(ACmd, AResponse);
s1 := IOHandler.ReadLn;
LCanContinue := True;
while (s1 <> '.') and LCanContinue do
begin
ParseNewsGroup(s1, sNewsgroup, lHi, lLo, sStatus);
ALisTIdEvent(sNewsgroup, lLo, lHi, sStatus, LCanContinue);
s1 := IOHandler.ReadLn;
end;
finally
EndWork(wmRead);
end;
end;
procedure TIdNNTP.GetNewsgroupList;
begin
if not Assigned(FOnNewsgroupList) then begin
raise EIdNNTPNoOnNewsgroupList.Create(RSNNTPNoOnNewsgroupList);
end;
ProcessGroupList('LIST', 215, FOnNewsgroupList); {do not localize}
end;
procedure TIdNNTP.GetNewGroupsList(ADate: TDateTime; AGMT: boolean;
ADistributions: string);
begin
if not Assigned(FOnNewGroupsList) then begin
raise EIdNNTPNoOnNewGroupsList.Create(RSNNTPNoOnNewGroupsList);
end;
ProcessGroupList('NEWGROUPS ' + ConvertDateTimeDist(ADate, AGMT, ADistributions), {do not localize}
231, FOnNewGroupsList);
end;
procedure TIdNNTP.GetNewNewsList(ANewsgroups: string;
ADate: TDateTime; AGMT: boolean; ADistributions: string);
var
s1: string;
CanContinue: Boolean;
begin
if not Assigned(FOnNewNewsList) then begin
raise EIdNNTPNoOnNewNewsList.Create(RSNNTPNoOnNewNewsList);
end;
BeginWork(wmRead,0); try
SendCmd('NEWNEWS ' + ANewsgroups + ' ' + ConvertDateTimeDist(ADate, AGMT, ADistributions), 230); {do not localize}
s1 := IOHandler.ReadLn;
CanContinue := True;
while (s1 <> '.') and CanContinue do begin
FOnNewNewsList(s1, CanContinue);
s1 := IOHandler.ReadLn;
end;
finally
EndWork(wmRead);
end;
end;
(*
3.9. The NEXT command
3.9.1. NEXT
NEXT
The internally maintained "current article pointer" is advanced to
the next article in the current newsgroup. If no more articles
remain in the current group, an error message is returned and the
current article remains selected.
The internally-maintained "current article pointer" is set by this
command.
A response indicating the current article number, and the message-id
string will be returned. No text is sent in response to this
command.
3.9.2. Responses
223 n a article retrieved - request text separately
(n = article number, a = unique article id)
412 no newsgroup selected
420 no current article has been selected
421 no next article in this group
*)
function TIdNNTP.Next: Boolean;
begin
Result := SendCmd('NEXT', [223, 421]) = 223; {do not localize}
end;
(*
3.5. The LAST command
3.5.1. LAST
LAST
The internally maintained "current article pointer" is set to the
previous article in the current newsgroup. If already positioned at
the first article of the newsgroup, an error message is returned and
the current article remains selected.
The internally-maintained "current article pointer" is set by this
command.
A response indicating the current article number, and a message-id
string will be returned. No text is sent in response to this
command.
3.5.2. Responses
223 n a article retrieved - request text separately
(n = article number, a = unique article id)
412 no newsgroup selected
420 no current article has been selected
422 no previous article in this group
*)
function TIdNNTP.Previous: Boolean;
begin
Result := SendCmd('LAST', [223, 422]) = 223; {do not localize}
end;
function TIdNNTP.SelectArticle(AMsgNo: Int64): Boolean;
begin
Result := SendCmd('STAT ' + IntToStr(AMsgNo), [223, 423]) = 223; {do not localize}
end;
procedure TIdNNTP.GetNewsgroupList(AList: TStrings);
var
LEncoding: IIdTextEncoding;
begin
SendCmd('LIST', 215); {do not localize}
LEncoding := IndyTextEncoding_8Bit;
IOHandler.Capture(AList, LEncoding{$IFDEF STRING_IS_ANSI}, LEncoding{$ENDIF});
end;
procedure TIdNNTP.GetNewGroupsList(ADate: TDateTime; AGMT: boolean;
ADistributions: string; AList: TStrings);
var
LEncoding: IIdTextEncoding;
begin
SendCmd('NEWGROUPS ' + ConvertDateTimeDist(ADate, AGMT, ADistributions), 231); {do not localize}
LEncoding := IndyTextEncoding_8Bit;
IOHandler.Capture(AList, LEncoding{$IFDEF STRING_IS_ANSI}, LEncoding{$ENDIF});
end;
procedure TIdNNTP.GetNewNewsList(ANewsgroups: string; ADate: TDateTime;
AGMT: boolean; ADistributions: string; AList: TStrings);
var
LEncoding: IIdTextEncoding;
begin
SendCmd('NEWNEWS ' + ANewsgroups + ' ' + ConvertDateTimeDist(ADate, AGMT, ADistributions), 230); {do not localize}
LEncoding := IndyTextEncoding_8Bit;
IOHandler.Capture(AList, LEncoding{$IFDEF STRING_IS_ANSI}, LEncoding{$ENDIF});
end;
function TIdNNTP.ConvertDateTimeDist(ADate: TDateTime; AGMT: boolean;
ADistributions: string): string;
begin
Result := FormatDateTime('yymmdd hhnnss', ADate); {do not localize}
if AGMT then begin
Result:= Result + ' GMT'; {do not localize}
end;
if Length(ADistributions) > 0 then begin
Result := ' <' + ADistributions + '>';
end;
end;
(*
3.1. The ARTICLE, BODY, HEAD, and STAT commands
There are two forms to the ARTICLE command (and the related BODY,
HEAD, and STAT commands), each using a different method of specifying
which article is to be retrieved. When the ARTICLE command is
followed by a message-id in angle brackets ("<" and ">"), the first
form of the command is used; when a numeric parameter or no parameter
is supplied, the second form is invoked.
The text of the article is returned as a textual response, as
described earlier in this document.
The HEAD and BODY commands are identical to the ARTICLE command
except that they respectively return only the header lines or text
body of the article.
The STAT command is similar to the ARTICLE command except that no
text is returned. When selecting by message number within a group,
the STAT command serves to set the current article pointer without
sending text. The returned acknowledgement response will contain the
message-id, which may be of some value. Using the STAT command to
select by message-id is valid but of questionable value, since a
selection by message-id does NOT alter the "current article pointer".
3.1.1. ARTICLE (selection by message-id)
ARTICLE <message-id>
Display the header, a blank line, then the body (text) of the
specified article. Message-id is the message id of an article as
shown in that article's header. It is anticipated that the client
will obtain the message-id from a list provided by the NEWNEWS
command, from references contained within another article, or from
the message-id provided in the response to some other commands.
Please note that the internally-maintained "current article pointer"
is NOT ALTERED by this command. This is both to facilitate the
presentation of articles that may be referenced within an article
being read, and because of the semantic difficulties of determining
the proper sequence and membership of an article which may have been
posted to more than one newsgroup.
3.1.2. ARTICLE (selection by number)
ARTICLE [nnn]
Displays the header, a blank line, then the body (text) of the
current or specified article. The optional parameter nnn is the
numeric id of an article in the current newsgroup and must be chosen
from the range of articles provided when the newsgroup was selected.
If it is omitted, the current article is assumed.
The internally-maintained "current article pointer" is set by this
command if a valid article number is specified.
[the following applies to both forms of the article command.] A
response indicating the current article number, a message-id string,
and that text is to follow will be returned.
The message-id string returned is an identification string contained
within angle brackets ("<" and ">"), which is derived from the header
of the article itself. The Message-ID header line (required by
RFC850) from the article must be used to supply this information. If
the message-id header line is missing from the article, a single
digit "0" (zero) should be supplied within the angle brackets.
Since the message-id field is unique with each article, it may be
used by a news reading program to skip duplicate displays of articles
that have been posted more than once, or to more than one newsgroup.
3.1.3. Responses
220 n <a> article retrieved - head and body follow
(n = article number, <a> = message-id)
221 n <a> article retrieved - head follows
222 n <a> article retrieved - body follows
223 n <a> article retrieved - request text separately
412 no newsgroup has been selected
420 no current article has been selected
423 no such article number in this group
430 no such article found
*)
function TIdNNTP.GetArticle(AMsg: TIdMessage): Boolean;
begin
Result := True;
SendCmd('ARTICLE', 220); {do not localize}
AMsg.Clear;
//Don't call ReceiveBody if the message ended at the end of the headers
//(ReceiveHeader() would have returned '.' in that case)...
if ReceiveHeader(AMsg) = '' then begin
ReceiveBody(AMsg);
end;
end;
function TIdNNTP.GetArticle(AMsgNo: Int64; AMsg: TIdMessage): Boolean;
begin
Result := SendCmd('ARTICLE ' + IntToStr(AMsgNo), [220, 423]) = 220; {do not localize}
if Result then begin
AMsg.Clear;
//Don't call ReceiveBody if the message ended at the end of the headers
//(ReceiveHeader() would have returned '.' in that case)...
if ReceiveHeader(AMsg) = '' then begin
ReceiveBody(AMsg);
end;
end;
end;
function TIdNNTP.GetArticle(AMsgID: string; AMsg: TIdMessage): Boolean;
begin
Result := SendCmd('ARTICLE ' + EnsureMsgIDBrackets(AMsgID), [220, 430]) = 220; {do not localize}
if Result then begin
AMsg.Clear;
//Don't call ReceiveBody if the message ended at the end of the headers
//(ReceiveHeader() would have returned '.' in that case)...
if ReceiveHeader(AMsg) = '' then begin
ReceiveBody(AMsg);
end;
end;
end;
function TIdNNTP.GetArticle(AMsg: TStrings): Boolean;
var
LEncoding: IIdTextEncoding;
begin
Result := True;
SendCmd('ARTICLE', 220); {do not localize}
AMsg.Clear;
// per RFC 3977, headers should be in UTF-8, but are not required to,
// so lets read them as 8-bit...
LEncoding := IndyTextEncoding_8Bit;
IOHandler.Capture(AMsg, LEncoding{$IFDEF STRING_IS_ANSI}, LEncoding{$ENDIF});
end;
function TIdNNTP.GetArticle(AMsgNo: Int64; AMsg: TStrings): Boolean;
begin
Result := SendCmd('ARTICLE ' + IntToStr(AMsgNo), [220, 423]) = 220; {do not localize}
if Result then begin
AMsg.Clear;
// per RFC 3977, headers should be in UTF-8, but are not required to,
// so lets read them as 8-bit...
IOHandler.Capture(AMsg, IndyTextEncoding_8Bit);
end;
end;
function TIdNNTP.GetArticle(AMsgID: string; AMsg: TStrings): Boolean;
var
LEncoding: IIdTextEncoding;
begin
Result := SendCmd('ARTICLE ' + EnsureMsgIDBrackets(AMsgID), [220, 430]) = 220; {do not localize}
if Result then begin
AMsg.Clear;
// per RFC 3977, headers should be in UTF-8, but are not required to,
// so lets read them as 8-bit...
LEncoding := IndyTextEncoding_8Bit;
IOHandler.Capture(AMsg, LEncoding{$IFDEF STRING_IS_ANSI}, LEncoding{$ENDIF});
end;
end;
function TIdNNTP.GetArticle(AMsg: TStream): Boolean;
var
LEncoding: IIdTextEncoding;
begin
Result := True;
SendCmd('ARTICLE', 220); {do not localize}
// per RFC 3977, headers should be in UTF-8, but are not required to,
// so lets read them as 8-bit...
LEncoding := IndyTextEncoding_8Bit;
IOHandler.Capture(AMsg, LEncoding{$IFDEF STRING_IS_ANSI}, LEncoding{$ENDIF});
end;
function TIdNNTP.GetArticle(AMsgNo: Int64; AMsg: TStream): Boolean;
var
LEncoding: IIdTextEncoding;
begin
Result := SendCmd('ARTICLE ' + IntToStr(AMsgNo), [220, 423]) = 220; {do not localize}
if Result then begin
// per RFC 3977, headers should be in UTF-8, but are not required to,
// so lets read them as 8-bit...
LEncoding := IndyTextEncoding_8Bit;
IOHandler.Capture(AMsg, LEncoding{$IFDEF STRING_IS_ANSI}, LEncoding{$ENDIF});
end;
end;
function TIdNNTP.GetArticle(AMsgID: string; AMsg: TStream): Boolean;
var
LEncoding: IIdTextEncoding;
begin
Result := SendCmd('ARTICLE ' + EnsureMsgIDBrackets(AMsgID), [220, 430]) = 220; {do not localize}
if Result then begin
// per RFC 3977, headers should be in UTF-8, but are not required to,
// so lets read them as 8-bit...
LEncoding := IndyTextEncoding_8Bit;
IOHandler.Capture(AMsg, LEncoding{$IFDEF STRING_IS_ANSI}, LEncoding{$ENDIF});
end;
end;
function TIdNNTP.GetBody(AMsg: TIdMessage): Boolean;
begin
Result := SendCmd('BODY', [222, 420]) = 222; {do not localize}
if Result then begin
AMsg.Clear;
ReceiveBody(AMsg);
end;
end;
function TIdNNTP.GetBody(AMsgNo: Int64; AMsg: TIdMessage): Boolean;
begin
Result := SendCmd('BODY ' + IntToStr(AMsgNo), [222, 423]) = 222; {do not localize}
if Result then begin
AMsg.Clear;
ReceiveBody(AMsg);
end;
end;
function TIdNNTP.GetBody(AMsgID: string; AMsg: TIdMessage): Boolean;
begin
Result := SendCmd('BODY ' + EnsureMsgIDBrackets(AMsgID), [222, 430]) = 222; {do not localize}
if Result then begin
AMsg.Clear;
ReceiveBody(AMsg);
end;
end;
function TIdNNTP.GetBody(AMsg: TStrings): Boolean;
var
LEncoding: IIdTextEncoding;
begin
Result := True;
SendCmd('BODY', 222); {do not localize}
AMsg.Clear;
LEncoding := IndyTextEncoding_8Bit;
IOHandler.Capture(AMsg, LEncoding{$IFDEF STRING_IS_ANSI}, LEncoding{$ENDIF});
end;
function TIdNNTP.GetBody(AMsgNo: Int64; AMsg: TStrings): Boolean;
var
LEncoding: IIdTextEncoding;
begin
Result := SendCmd('BODY ' + IntToStr(AMsgNo), [222, 423]) = 222; {do not localize}
if Result then begin
AMsg.Clear;
LEncoding := IndyTextEncoding_8Bit;
IOHandler.Capture(AMsg, LEncoding{$IFDEF STRING_IS_ANSI}, LEncoding{$ENDIF});
end;
end;
function TIdNNTP.GetBody(AMsgID: string; AMsg: TStrings): Boolean;
var
LEncoding: IIdTextEncoding;
begin
Result := SendCmd('BODY ' + EnsureMsgIDBrackets(AMsgID), [222, 430]) = 222; {do not localize}
if Result then begin
AMsg.Clear;
LEncoding := IndyTextEncoding_8Bit;
IOHandler.Capture(AMsg, LEncoding{$IFDEF STRING_IS_ANSI}, LEncoding{$ENDIF});
end;
end;
function TIdNNTP.GetBody(AMsg: TStream): Boolean;
var
LEncoding: IIdTextEncoding;
begin
Result := True;
SendCmd('BODY', 222); {do not localize}
LEncoding := IndyTextEncoding_8Bit;
IOHandler.Capture(AMsg, LEncoding{$IFDEF STRING_IS_ANSI}, LEncoding{$ENDIF});
end;
function TIdNNTP.GetBody(AMsgNo: Int64; AMsg: TStream): Boolean;
var
LEncoding: IIdTextEncoding;
begin
Result := SendCmd('BODY ' + IntToStr(AMsgNo), [222, 423]) = 222; {do not localize}
if Result then begin
LEncoding := IndyTextEncoding_8Bit;
IOHandler.Capture(AMsg, LEncoding{$IFDEF STRING_IS_ANSI}, LEncoding{$ENDIF});
end;
end;
function TIdNNTP.GetBody(AMsgID: string; AMsg: TStream): Boolean;
var
LEncoding: IIdTextEncoding;
begin
Result := SendCmd('BODY ' + EnsureMsgIDBrackets(AMsgID), [222, 430]) = 222; {do not localize}
if Result then begin
LEncoding := IndyTextEncoding_8Bit;
IOHandler.Capture(AMsg, LEncoding{$IFDEF STRING_IS_ANSI}, LEncoding{$ENDIF});
end;
end;
function TIdNNTP.GetHeader(AMsg: TIdMessage): Boolean;
begin
Result := True;
SendCmd('HEAD', 221); {do not localize}
AMsg.Clear;
ReceiveHeader(AMsg);
end;
function TIdNNTP.GetHeader(AMsgNo: Int64; AMsg: TIdMessage): Boolean;
begin
Result := SendCmd('HEAD ' + IntToStr(AMsgNo), [221, 423]) = 221; {do not localize}
if Result then begin
AMsg.Clear;
ReceiveHeader(AMsg);
end;
end;
function TIdNNTP.GetHeader(AMsgID: string; AMsg: TIdMessage): Boolean;
begin
Result := SendCmd('HEAD ' + EnsureMsgIDBrackets(AMsgID), [221, 430]) = 221; {do not localize}
if Result then begin
AMsg.Clear;
ReceiveHeader(AMsg);
end;
end;
function TIdNNTP.GetHeader(AMsg: TStrings): Boolean;
var
LEncoding: IIdTextEncoding;
begin
Result := True;
SendCmd('HEAD', 221); {do not localize}
AMsg.Clear;
// per RFC 3977, headers should be in UTF-8, but are not required to,
// so lets read them as 8-bit...
LEncoding := IndyTextEncoding_8Bit;
IOHandler.Capture(AMsg, LEncoding{$IFDEF STRING_IS_ANSI}, LEncoding{$ENDIF});
end;
function TIdNNTP.GetHeader(AMsgNo: Int64; AMsg: TStrings): Boolean;
var
LEncoding: IIdTextEncoding;
begin
Result := SendCmd('HEAD ' + IntToStr(AMsgNo), [221, 423]) = 221; {do not localize}
if Result then begin
AMsg.Clear;
// per RFC 3977, headers should be in UTF-8, but are not required to,
// so lets read them as 8-bit...
LEncoding := IndyTextEncoding_8Bit;
IOHandler.Capture(AMsg, LEncoding{$IFDEF STRING_IS_ANSI}, LEncoding{$ENDIF});
end;
end;
function TIdNNTP.GetHeader(AMsgID: string; AMsg: TStrings): Boolean;
var
LEncoding: IIdTextEncoding;
begin
Result := SendCmd('HEAD ' + EnsureMsgIDBrackets(AMsgID), [221, 430]) = 221; {do not localize}
if Result then begin
AMsg.Clear;
// per RFC 3977, headers should be in UTF-8, but are not required to,
// so lets read them as 8-bit...
LEncoding := IndyTextEncoding_8Bit;
IOHandler.Capture(AMsg, LEncoding{$IFDEF STRING_IS_ANSI}, LEncoding{$ENDIF});
end;
end;
function TIdNNTP.GetHeader(AMsg: TStream): Boolean;
var
LEncoding: IIdTextEncoding;
begin
Result := True;
SendCmd('HEAD', 221); {do not localize}
// per RFC 3977, headers should be in UTF-8, but are not required to,
// so lets read them as 8-bit...
LEncoding := IndyTextEncoding_8Bit;
IOHandler.Capture(AMsg, LEncoding{$IFDEF STRING_IS_ANSI}, LEncoding{$ENDIF});
end;
function TIdNNTP.GetHeader(AMsgNo: Int64; AMsg: TStream): Boolean;
var
LEncoding: IIdTextEncoding;
begin
Result := SendCmd('HEAD ' + IntToStr(AMsgNo), [221, 423]) = 221; {do not localize}
if Result then begin
// per RFC 3977, headers should be in UTF-8, but are not required to,
// so lets read them as 8-bit...
LEncoding := IndyTextEncoding_8Bit;
IOHandler.Capture(AMsg, LEncoding{$IFDEF STRING_IS_ANSI}, LEncoding{$ENDIF});
end;
end;
function TIdNNTP.GetHeader(AMsgID: string; AMsg: TStream): Boolean;
var
LEncoding: IIdTextEncoding;
begin
Result := SendCmd('HEAD ' + EnsureMsgIDBrackets(AMsgID), [221, 430]) = 221; {do not localize}
if Result then begin
// per RFC 3977, headers should be in UTF-8, but are not required to,
// so lets read them as 8-bit...
LEncoding := IndyTextEncoding_8Bit;
IOHandler.Capture(AMsg, LEncoding{$IFDEF STRING_IS_ANSI}, LEncoding{$ENDIF});
end;
end;
procedure TIdNNTP.GetNewsgroupList(AStream: TStream);
var
LEncoding: IIdTextEncoding;
begin
SendCmd('LIST', 215); {do not localize}
LEncoding := IndyTextEncoding_8Bit;
IOHandler.Capture(AStream, LEncoding{$IFDEF STRING_IS_ANSI}, LEncoding{$ENDIF});
end;
procedure TIdNNTP.AfterConnect;
begin
try
// Here lets check to see what condition we are in after being greeted by
// the server. The application utilizing NNTPWinshoe should check the value
// of GreetingResult to determine if further action is warranted.
case FGreetingCode of
200: FPermission := crCanPost;
201: FPermission := crNoPost;
400: FPermission := crTempUnavailable;
// This should never happen because the server should immediately close
// the connection but just in case ....
// Kudzu: Changed this to an exception, otherwise it produces non-standard usage by the
// users code
502: raise EIdNNTPConnectionRefused.CreateError(502, RSNNTPConnectionRefused);
end;
// here we call SeTIdMode on the value stored in mode to make sure we can
// use the mode we have selected
case Mode of
mtStream: begin
SendCmd('MODE STREAM'); {do not localize}
if LastCmdResult.NumericCode <> 203 then begin
ModeResult := mrNoStream
end else begin
ModeResult := mrCanStream;
end;
end;
mtReader: begin
// We should get the same info we got in the greeting
// result but we set mode to reader anyway since the
// server may want to do some internal reconfiguration
// if it knows that a reader has connected
SendCmd('MODE READER'); {do not localize}
if LastCmdResult.NumericCode <> 200 then begin
ModeResult := mrNoPost;
end else begin
ModeResult := mrCanPost;
end;
end;
end;
GetCapability;
except
Disconnect;
Raise;
end;
end;
destructor TIdNNTP.Destroy;
begin
inherited Destroy;
end;
procedure TIdNNTP.GetCapability;
var
i: Integer;
begin
FCapabilities.Clear;
// try CAPABILITIES first, as it is a standard command introduced in RFC 3977
if SendCmd('CAPABILITIES') = 101 then {do not localize}
begin
IOHandler.Capture(FCapabilities, '.'); {do not localize}
end
// fall back to the previous non-standard approach
else if SendCmd('LIST EXTENSIONS') in [202, 215] then {do not localize}
begin
IOHandler.Capture(FCapabilities, '.'); {do not localize}
end;
//flatten everything out for easy processing
for i := 0 to FCapabilities.Count-1 do
begin
FCapabilities[i] := Trim(UpperCase(FCapabilities[i]));
end;
FOVERSupported := IsExtCmdSupported('OVER'); {do not localize}
FHDRSupported := IsExtCmdSupported('HDR'); {do not localize}
// Self.FStartTLSSupported := IsExtCmdSupported('STARTTLS');
end;
function TIdNNTP.IsExtCmdSupported(AExtension: String): Boolean;
begin
Result := FCapabilities.IndexOf(Trim(UpperCase(AExtension))) > -1;
end;
procedure TIdNNTP.StartTLS;
var
LIO : TIdSSLIOHandlerSocketBase;
begin
if (IOHandler is TIdSSLIOHandlerSocketBase) and (FUseTLS <> utNoTLSSupport) then
begin
LIO := TIdSSLIOHandlerSocketBase(IOHandler);
//we check passthrough because we can either be using TLS currently with
//implicit TLS support or because STARTLS was issued previously.
if LIO.PassThrough then
begin
if IsExtCmdSupported('STARTTLS') then {do not localize}
begin
if SendCmd('STARTTLS') = 382 then {do not localize}
begin
TLSHandshake;
AfterConnect;
end else begin
ProcessTLSNegCmdFailed;
end;
end else begin
ProcessTLSNotAvail;
end;
end;
end;
end;
function TIdNNTP.GetSupportsTLS: boolean;
begin
Result := IsExtCmdSupported('STARTTLS') {do not localize}
end;
procedure TIdNNTP.XHDR(AHeader, AParam: string);
var
LLine : String;
LMsg, LHeaderData : String;
LCanContinue : Boolean;
begin
if Assigned(FOnXHDREntry) then
begin
XHDRCommon(AHeader,AParam);
BeginWork(wmRead, 0);
try
LLine := IOHandler.ReadLn;
LCanContinue := True;
while (LLine <> '.') and LCanContinue do
begin
ParseXHDRLine(LLine,LMsg,LHeaderData);
FOnXHDREntry(AHeader,LMsg,LHeaderData,LCanContinue);
LLine := IOHandler.ReadLn;
end;
finally
EndWork(wmRead);
end;
end else
begin
raise EIdNNTPNoOnXHDREntry.Create(RSNNTPNoOnXHDREntry);
end;
end;
procedure TIdNNTP.XOVER(AParam: string);
var
LLine : String;
//for our XOVER data
LArticleIndex : Int64;
LSubject,
LFrom : String;
LDate : TDateTime;
LMsgId, LReferences : String;
LByteCount,
LLineCount : Integer;
LExtraData : String;
LCanContinue : Boolean;
begin
if Assigned( FOnXOVER) then
begin
XOVERCommon(AParam);
BeginWork(wmRead, 0);
try
LLine := IOHandler.ReadLn;
LCanContinue := True;
while (LLine <> '.') and LCanContinue do
begin
ParseXOVER(LLine,LArticleIndex,LSubject,LFrom,LDate,
LMsgId,LReferences,LByteCount,LLineCount,LExtraData);
FOnXOVER(LArticleIndex,LSubject,LFrom,LDate,LMsgId,LReferences,LByteCount,LLineCount,LExtraData,LCanContinue);
LLine := IOHandler.ReadLn;
end;
finally
EndWork(wmRead);
end;
end else
begin
raise EIdNNTPNoOnXOVER.Create(RSNNTPNoOnXOVER);
end;
end;
procedure TIdNNTP.ParseXHDRLine(ALine: String; out AMsg,
AHeaderData: String);
begin
//from: RFC 2890
//Each line
//containing matched headers returned by the server has an article
//number (or message ID, if a message ID was specified in the command),
//then one or more spaces, then the value of the requested header in
//that article.
//from: http://www.ietf.org/internet-drafts/draft-ietf-nntpext-base-18.txt
// describing HDR
// The line consists
// of the article number, a space, and then the contents of the header
// (without the header name or the colon and space that follow it) or
// metadata item. If the article is specified by message-id rather than
// by article range, the article number is given as "0".
AMsg := Fetch(ALine);
AHeaderData := ALine;
end;
procedure TIdNNTP.XHDRCommon(AHeader, AParam : String);
begin
if FHDRSupported then
begin
//http://www.ietf.org/internet-drafts/draft-ietf-nntpext-base-18.txt
//says the correct reply code is 225 but RFC 2980 specifies 221 for the
//XHDR command so we should accept both to CYA.
SendCmd('HDR '+ AHeader + ' ' + AParam, [225, 221]); {do not localize}
end else
begin
SendCmd('XHDR ' + AHeader + ' ' + AParam, 221); {do not localize}
end;
end;
procedure TIdNNTP.XOVERCommon(AParam: String);
begin
if FOVERSupported then begin
SendCmd('OVER '+ AParam, 224); {do not localize}
end else begin
SendCmd('XOVER ' + AParam, 224); {do not localize}
end;
end;
procedure TIdNNTP.DisconnectNotifyPeer;
begin
inherited DisconnectNotifyPeer;
SendCmd('QUIT', 205); {do not localize}
end;
procedure TIdNNTP.SendAuth;
begin
// RLebeau - RFC 2980 says that if the password is not required,
// then 281 will be returned for the username request, not 381.
if (inherited SendCmd('AUTHINFO USER ' + Username, [281, 381]) = 381) then begin {do not localize}
inherited SendCmd('AUTHINFO PASS ' + Password, 281); {do not localize}
end;
end;
end.