{ $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 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 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 . 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 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 article retrieved - head and body follow (n = article number, = message-id) 221 n article retrieved - head follows 222 n article retrieved - body follows 223 n 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.