IdCmdTCPServer.pas 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535
  1. {
  2. $Project$
  3. $Workfile$
  4. $Revision$
  5. $DateUTC$
  6. $Id$
  7. This file is part of the Indy (Internet Direct) project, and is offered
  8. under the dual-licensing agreement described on the Indy website.
  9. (http://www.indyproject.org/)
  10. Copyright:
  11. (c) 1993-2005, Chad Z. Hower and the Indy Pit Crew. All rights reserved.
  12. }
  13. {
  14. $Log$
  15. }
  16. {
  17. Rev 1.42 2/1/05 12:36:36 AM RLebeau
  18. Removed CommandHandlersEnabled property, no longer used
  19. Rev 1.41 12/2/2004 9:26:42 PM JPMugaas
  20. Bug fix.
  21. Rev 1.40 2004.10.27 9:20:04 AM czhower
  22. For TIdStrings
  23. Rev 1.39 10/26/2004 8:42:58 PM JPMugaas
  24. Should be more portable with new references to TIdStrings and TIdStringList.
  25. Rev 1.38 6/21/04 10:07:14 PM RLebeau
  26. Updated .DoConnect() to make sure the connection is still connected before
  27. then sending the Greeting
  28. Rev 1.37 6/20/2004 12:01:44 AM DSiders
  29. Added "Do Not Localize" comments.
  30. Rev 1.36 6/16/04 12:37:06 PM RLebeau
  31. more compiler errors
  32. Rev 1.35 6/16/04 12:30:32 PM RLebeau
  33. compiler errors
  34. Rev 1.34 6/16/04 12:12:26 PM RLebeau
  35. Updated ExceptionReply, Greeting, HelpReply, MaxConnectionReply, and
  36. ReplyUnknownCommand properties to use getter methods that call virtual Create
  37. methods which descendants can override for class-specific initializations
  38. Rev 1.33 5/16/04 5:16:52 PM RLebeau
  39. Added setter methods to ExceptionReply, HelpReply, and ReplyTexts properties
  40. Rev 1.32 4/19/2004 5:39:58 PM BGooijen
  41. Added comment
  42. Rev 1.31 4/18/2004 11:58:44 PM BGooijen
  43. Wasn't thread safe
  44. Rev 1.30 3/3/2004 4:59:38 AM JPMugaas
  45. Updated for new properties.
  46. Rev 1.29 2004.03.01 5:12:24 PM czhower
  47. -Bug fix for shutdown of servers when connections still existed (AV)
  48. -Implicit HELP support in CMDserver
  49. -Several command handler bugs
  50. -Additional command handler functionality.
  51. Rev 1.28 2004.02.29 9:43:08 PM czhower
  52. Added ReadCommandLine.
  53. Rev 1.27 2004.02.29 8:17:18 PM czhower
  54. Minor cosmetic changes to code.
  55. Rev 1.26 2004.02.03 4:17:08 PM czhower
  56. For unit name changes.
  57. Rev 1.25 03/02/2004 01:49:22 CCostelloe
  58. Added DoReplyUnknownCommand to allow TIdIMAP4Server set a correct reply for
  59. unknown commands
  60. Rev 1.24 1/29/04 9:43:16 PM RLebeau
  61. Added setter methods to various TIdReply properties
  62. Rev 1.23 2004.01.20 10:03:22 PM czhower
  63. InitComponent
  64. Rev 1.22 1/5/2004 2:35:36 PM JPMugaas
  65. Removed of object in method declarations.
  66. Rev 1.21 1/5/04 10:12:58 AM RLebeau
  67. Fixed Typos in OnBeforeCommandHandler and OnAfterCommandHandler events
  68. Rev 1.20 1/4/04 8:45:34 PM RLebeau
  69. Added OnBeforeCommandHandler and OnAfterCommandHandler events
  70. Rev 1.19 1/1/2004 9:33:22 PM BGooijen
  71. the abstract class TIdReply was created sometimes, fixed that
  72. Rev 1.18 2003.10.18 9:33:26 PM czhower
  73. Boatload of bug fixes to command handlers.
  74. Rev 1.17 2003.10.18 8:03:58 PM czhower
  75. Defaults for codes
  76. Rev 1.16 8/31/2003 11:49:40 AM BGooijen
  77. removed FReplyClass, this was also in TIdTCPServer
  78. Rev 1.15 7/9/2003 10:55:24 PM BGooijen
  79. Restored all features
  80. Rev 1.14 7/9/2003 04:36:08 PM JPMugaas
  81. You now can override the TIdReply with your own type. This should illiminate
  82. some warnings about some serious issues. TIdReply is ONLY a base class with
  83. virtual methods.
  84. Rev 1.13 2003.07.08 2:26:02 PM czhower
  85. Sergio's update
  86. Rev 1.0 7/7/2003 7:06:44 PM SPerry
  87. Component that uses command handlers
  88. Rev 1.0 7/6/2003 4:47:32 PM SPerry
  89. Units that use Command handlers
  90. Adapted to IdCommandHandlers.pas SPerry
  91. Rev 1.7 4/4/2003 8:08:00 PM BGooijen
  92. moved some consts from tidtcpserver here
  93. Rev 1.6 3/23/2003 11:22:24 PM BGooijen
  94. Moved some code to HandleCommand
  95. Rev 1.5 3/22/2003 1:46:36 PM BGooijen
  96. Removed unused variables
  97. Rev 1.4 3/20/2003 12:18:30 PM BGooijen
  98. Moved ReplyExceptionCode from TIdTCPServer to TIdCmdTCPServer
  99. Rev 1.3 3/20/2003 12:14:18 PM BGooijen
  100. Re-enabled Server.ReplyException
  101. Rev 1.2 2/24/2003 07:21:50 PM JPMugaas
  102. Now compiles with new core code restructures.
  103. Rev 1.1 1/23/2003 11:06:10 AM BGooijen
  104. Rev 1.0 1/20/2003 12:48:40 PM BGooijen
  105. Tcpserver with command handlers, these were originally in TIdTcpServer, but
  106. are now moved here
  107. }
  108. unit IdCmdTCPServer;
  109. interface
  110. {$I IdCompilerDefines.inc}
  111. //Put FPC into Delphi mode
  112. uses
  113. Classes,
  114. IdCommandHandlers,
  115. IdContext,
  116. IdIOHandler,
  117. IdReply,
  118. IdTCPServer,
  119. SysUtils;
  120. type
  121. TIdCmdTCPServer = class;
  122. { Events }
  123. TIdCmdTCPServerAfterCommandHandlerEvent = procedure(ASender: TIdCmdTCPServer;
  124. AContext: TIdContext) of object;
  125. TIdCmdTCPServerBeforeCommandHandlerEvent = procedure(ASender: TIdCmdTCPServer;
  126. var AData: string; AContext: TIdContext) of object;
  127. TIdCmdTCPServer = class(TIdTCPServer)
  128. protected
  129. FCommandHandlers: TIdCommandHandlers;
  130. FCommandHandlersInitialized: Boolean;
  131. FExceptionReply: TIdReply;
  132. FHelpReply: TIdReply;
  133. FGreeting: TIdReply;
  134. FMaxConnectionReply: TIdReply;
  135. FOnAfterCommandHandler: TIdCmdTCPServerAfterCommandHandlerEvent;
  136. FOnBeforeCommandHandler: TIdCmdTCPServerBeforeCommandHandlerEvent;
  137. FReplyClass: TIdReplyClass;
  138. FReplyTexts: TIdReplies;
  139. FReplyUnknownCommand: TIdReply;
  140. //
  141. procedure CheckOkToBeActive; override;
  142. function CreateExceptionReply: TIdReply; virtual;
  143. function CreateGreeting: TIdReply; virtual;
  144. function CreateHelpReply: TIdReply; virtual;
  145. function CreateMaxConnectionReply: TIdReply; virtual;
  146. function CreateReplyUnknownCommand: TIdReply; virtual;
  147. procedure DoAfterCommandHandler(ASender: TIdCommandHandlers; AContext: TIdContext);
  148. procedure DoBeforeCommandHandler(ASender: TIdCommandHandlers; var AData: string;
  149. AContext: TIdContext);
  150. procedure DoConnect(AContext: TIdContext); override;
  151. function DoExecute(AContext: TIdContext): Boolean; override;
  152. procedure DoMaxConnectionsExceeded(AIOHandler: TIdIOHandler); override;
  153. // This is here to allow servers to override this functionality, such as IMAP4 server
  154. procedure DoReplyUnknownCommand(AContext: TIdContext; ALine: string); virtual;
  155. function GetExceptionReply: TIdReply;
  156. function GetGreeting: TIdReply;
  157. function GetHelpReply: TIdReply;
  158. function GetMaxConnectionReply: TIdReply;
  159. function GetRepliesClass: TIdRepliesClass; virtual;
  160. function GetReplyClass: TIdReplyClass; virtual;
  161. function GetReplyUnknownCommand: TIdReply;
  162. procedure InitializeCommandHandlers; virtual;
  163. procedure InitComponent; override;
  164. // This is used by command handlers as the only input. This can be overriden to filter, modify,
  165. // or preparse the input.
  166. function ReadCommandLine(AContext: TIdContext): string; virtual;
  167. procedure Startup; override;
  168. procedure SetCommandHandlers(AValue: TIdCommandHandlers);
  169. procedure SetExceptionReply(AValue: TIdReply);
  170. procedure SetGreeting(AValue: TIdReply);
  171. procedure SetHelpReply(AValue: TIdReply);
  172. procedure SetMaxConnectionReply(AValue: TIdReply);
  173. procedure SetReplyUnknownCommand(AValue: TIdReply);
  174. procedure SetReplyTexts(AValue: TIdReplies);
  175. public
  176. destructor Destroy; override;
  177. published
  178. property CommandHandlers: TIdCommandHandlers read FCommandHandlers
  179. write SetCommandHandlers;
  180. property ExceptionReply: TIdReply read GetExceptionReply write SetExceptionReply;
  181. property Greeting: TIdReply read GetGreeting write SetGreeting;
  182. property HelpReply: TIdReply read GetHelpReply write SetHelpReply;
  183. property MaxConnectionReply: TIdReply read GetMaxConnectionReply
  184. write SetMaxConnectionReply;
  185. property ReplyTexts: TIdReplies read FReplyTexts write SetReplyTexts;
  186. property ReplyUnknownCommand: TIdReply read GetReplyUnknownCommand
  187. write SetReplyUnknownCommand;
  188. //
  189. property OnAfterCommandHandler: TIdCmdTCPServerAfterCommandHandlerEvent
  190. read FOnAfterCommandHandler write FOnAfterCommandHandler;
  191. property OnBeforeCommandHandler: TIdCmdTCPServerBeforeCommandHandlerEvent
  192. read FOnBeforeCommandHandler write FOnBeforeCommandHandler;
  193. end;
  194. implementation
  195. uses
  196. IdGlobal,
  197. IdResourceStringsCore,
  198. IdReplyRFC;
  199. function TIdCmdTCPServer.GetReplyClass: TIdReplyClass;
  200. begin
  201. Result := TIdReplyRFC;
  202. end;
  203. function TIdCmdTCPServer.GetRepliesClass: TIdRepliesClass;
  204. begin
  205. Result := TIdRepliesRFC;
  206. end;
  207. destructor TIdCmdTCPServer.Destroy;
  208. begin
  209. inherited Destroy;
  210. FreeAndNil(FReplyUnknownCommand);
  211. FreeAndNil(FReplyTexts);
  212. FreeAndNil(FMaxConnectionReply);
  213. FreeAndNil(FHelpReply);
  214. FreeAndNil(FGreeting);
  215. FreeAndNil(FExceptionReply);
  216. FreeAndNil(FCommandHandlers);
  217. end;
  218. procedure TIdCmdTCPServer.DoAfterCommandHandler(ASender: TIdCommandHandlers;
  219. AContext: TIdContext);
  220. begin
  221. if Assigned(OnAfterCommandHandler) then begin
  222. OnAfterCommandHandler(Self, AContext);
  223. end;
  224. end;
  225. procedure TIdCmdTCPServer.DoBeforeCommandHandler(ASender: TIdCommandHandlers;
  226. var AData: string; AContext: TIdContext);
  227. begin
  228. if Assigned(OnBeforeCommandHandler) then begin
  229. OnBeforeCommandHandler(Self, AData, AContext);
  230. end;
  231. end;
  232. function TIdCmdTCPServer.DoExecute(AContext: TIdContext): Boolean;
  233. var
  234. LLine: string;
  235. begin
  236. if CommandHandlers.Count > 0 then begin
  237. Result := True;
  238. if AContext.Connection.Connected then begin
  239. LLine := ReadCommandLine(AContext);
  240. // OLX sends blank lines during reset groups (NNTP) and expects no response.
  241. // Not sure what the RFCs say about blank lines.
  242. // I telnetted to some newsservers, and they dont respond to blank lines.
  243. // This unit is core and not NNTP, but we should be consistent.
  244. if LLine <> '' then begin
  245. if not FCommandHandlers.HandleCommand(AContext, LLine) then begin
  246. DoReplyUnknownCommand(AContext, LLine);
  247. end;
  248. end;
  249. end;
  250. end else begin
  251. Result := inherited DoExecute(AContext);
  252. end;
  253. if Result and Assigned(AContext.Connection) then begin
  254. Result := AContext.Connection.Connected;
  255. end;
  256. // the return value is used to determine if the DoExecute needs to be called again by the thread
  257. end;
  258. procedure TIdCmdTCPServer.DoReplyUnknownCommand(AContext: TIdContext; ALine: string);
  259. var
  260. LReply: TIdReply;
  261. begin
  262. if CommandHandlers.PerformReplies then begin
  263. LReply := FReplyClass.CreateWithReplyTexts(nil, ReplyTexts); try
  264. LReply.Assign(ReplyUnknownCommand);
  265. LReply.Text.Add(ALine);
  266. AContext.Connection.IOHandler.Write(LReply.FormattedReply);
  267. finally
  268. FreeAndNil(LReply);
  269. end;
  270. end;
  271. end;
  272. procedure TIdCmdTCPServer.InitializeCommandHandlers;
  273. begin
  274. end;
  275. procedure TIdCmdTCPServer.DoConnect(AContext: TIdContext);
  276. var
  277. LGreeting: TIdReply;
  278. begin
  279. inherited DoConnect(AContext);
  280. // RLebeau - check the connection first in case the application
  281. // chose to disconnect the connection in the OnConnect event handler.
  282. if AContext.Connection.Connected then begin
  283. if Greeting.ReplyExists then begin
  284. ReplyTexts.UpdateText(Greeting);
  285. LGreeting := FReplyClass.Create(nil); try // SendGreeting calls TIdReply.GetFormattedReply
  286. LGreeting.Assign(Greeting); // and that changes the reply object, so we have to
  287. SendGreeting(AContext, LGreeting); // clone it to make it thread-safe
  288. finally
  289. FreeAndNil(LGreeting);
  290. end;
  291. end;
  292. end;
  293. end;
  294. procedure TIdCmdTCPServer.DoMaxConnectionsExceeded(AIOHandler: TIdIOHandler);
  295. begin
  296. inherited DoMaxConnectionsExceeded(AIOHandler);
  297. //Do not UpdateText here - in thread. Is done in constructor
  298. AIOHandler.Write(MaxConnectionReply.FormattedReply);
  299. end;
  300. procedure TIdCmdTCPServer.Startup;
  301. var
  302. i, j: Integer;
  303. LDescr: TStrings;
  304. LHelpList: TStringList;
  305. LHandler, LAddedHandler: TIdCommandHandler;
  306. begin
  307. inherited Startup;
  308. if not FCommandHandlersInitialized then begin
  309. // InitializeCommandHandlers must be called only at runtime, and only after streaming
  310. // has occured. This used to be in .Loaded and that worked for forms. It failed
  311. // for dynamically created instances and also for descendant classes.
  312. FCommandHandlersInitialized := True;
  313. InitializeCommandHandlers;
  314. if HelpReply.Code <> '' then begin
  315. LAddedHandler := CommandHandlers.Add;
  316. LAddedHandler.Command := 'Help'; {do not localize}
  317. LAddedHandler.Description.Text := 'Displays commands that the servers supports.'; {do not localize}
  318. LAddedHandler.NormalReply.Assign(HelpReply);
  319. LHelpList := TStringList.Create;
  320. try
  321. for i := 0 to CommandHandlers.Count - 1 do begin
  322. LHandler := CommandHandlers.Items[i];
  323. if LHandler.HelpVisible then begin
  324. LHelpList.AddObject(LHandler.Command+LHandler.HelpSuperScript, LHandler);
  325. end;
  326. end;
  327. LHelpList.Sort;
  328. for i := 0 to LHelpList.Count - 1 do begin
  329. LAddedHandler.Response.Add(LHelpList[i]);
  330. LDescr := TIdCommandHandler(LHelpList.Objects[i]).Description;
  331. for j := 0 to LDescr.Count - 1 do begin
  332. LAddedHandler.Response.Add(' ' + LDescr[j]); {do not localize}
  333. end;
  334. LAddedHandler.Response.Add(''); {do not localize}
  335. end;
  336. finally
  337. FreeAndNil(LHelpList);
  338. end;
  339. end;
  340. end;
  341. end;
  342. procedure TIdCmdTCPServer.SetCommandHandlers(AValue: TIdCommandHandlers);
  343. begin
  344. FCommandHandlers.Assign(AValue);
  345. end;
  346. function TIdCmdTCPServer.CreateExceptionReply: TIdReply;
  347. begin
  348. Result := FReplyClass.CreateWithReplyTexts(nil, ReplyTexts);
  349. Result.SetReply(500, 'Unknown Internal Error'); {do not localize}
  350. end;
  351. function TIdCmdTCPServer.GetExceptionReply: TIdReply;
  352. begin
  353. if FExceptionReply = nil then begin
  354. FExceptionReply := CreateExceptionReply;
  355. end;
  356. Result := FExceptionReply;
  357. end;
  358. procedure TIdCmdTCPServer.SetExceptionReply(AValue: TIdReply);
  359. begin
  360. ExceptionReply.Assign(AValue);
  361. end;
  362. function TIdCmdTCPServer.CreateGreeting: TIdReply;
  363. begin
  364. Result := FReplyClass.CreateWithReplyTexts(nil, ReplyTexts);
  365. Result.SetReply(200, 'Welcome'); {do not localize}
  366. end;
  367. function TIdCmdTCPServer.GetGreeting: TIdReply;
  368. begin
  369. if FGreeting = nil then begin
  370. FGreeting := CreateGreeting;
  371. end;
  372. Result := FGreeting;
  373. end;
  374. procedure TIdCmdTCPServer.SetGreeting(AValue: TIdReply);
  375. begin
  376. Greeting.Assign(AValue);
  377. end;
  378. function TIdCmdTCPServer.CreateHelpReply: TIdReply;
  379. begin
  380. Result := FReplyClass.CreateWithReplyTexts(nil, ReplyTexts);
  381. Result.SetReply(100, 'Help follows'); {do not localize}
  382. end;
  383. function TIdCmdTCPServer.GetHelpReply: TIdReply;
  384. begin
  385. if FHelpReply = nil then begin
  386. FHelpReply := CreateHelpReply;
  387. end;
  388. Result := FHelpReply;
  389. end;
  390. procedure TIdCmdTCPServer.SetHelpReply(AValue: TIdReply);
  391. begin
  392. HelpReply.Assign(AValue);
  393. end;
  394. function TIdCmdTCPServer.CreateMaxConnectionReply: TIdReply;
  395. begin
  396. Result := FReplyClass.CreateWithReplyTexts(nil, ReplyTexts);
  397. Result.SetReply(300, 'Too many connections. Try again later.'); {do not localize}
  398. end;
  399. function TIdCmdTCPServer.GetMaxConnectionReply: TIdReply;
  400. begin
  401. if FMaxConnectionReply = nil then begin
  402. FMaxConnectionReply := CreateMaxConnectionReply;
  403. end;
  404. Result := FMaxConnectionReply;
  405. end;
  406. procedure TIdCmdTCPServer.SetMaxConnectionReply(AValue: TIdReply);
  407. begin
  408. MaxConnectionReply.Assign(AValue);
  409. end;
  410. function TIdCmdTCPServer.CreateReplyUnknownCommand: TIdReply;
  411. begin
  412. Result := FReplyClass.CreateWithReplyTexts(nil, ReplyTexts);
  413. Result.SetReply(400, 'Unknown Command'); {do not localize}
  414. end;
  415. function TIdCmdTCPServer.GetReplyUnknownCommand: TIdReply;
  416. begin
  417. if FReplyUnknownCommand = nil then begin
  418. FReplyUnknownCommand := CreateReplyUnknownCommand;
  419. end;
  420. Result := FReplyUnknownCommand;
  421. end;
  422. procedure TIdCmdTCPServer.SetReplyUnknownCommand(AValue: TIdReply);
  423. begin
  424. ReplyUnknownCommand.Assign(AValue);
  425. end;
  426. procedure TIdCmdTCPServer.SetReplyTexts(AValue: TIdReplies);
  427. begin
  428. FReplyTexts.Assign(AValue);
  429. end;
  430. procedure TIdCmdTCPServer.InitComponent;
  431. begin
  432. inherited InitComponent;
  433. FReplyClass := GetReplyClass;
  434. // Before Command handlers as they need FReplyTexts, but after FReplyClass is set
  435. FReplyTexts := GetRepliesClass.Create(Self, FReplyClass);
  436. FCommandHandlers := TIdCommandHandlers.Create(Self, FReplyClass, ReplyTexts, ExceptionReply);
  437. FCommandHandlers.OnAfterCommandHandler := DoAfterCommandHandler;
  438. FCommandHandlers.OnBeforeCommandHandler := DoBeforeCommandHandler;
  439. end;
  440. function TIdCmdTCPServer.ReadCommandLine(AContext: TIdContext): string;
  441. begin
  442. Result := AContext.Connection.IOHandler.ReadLn;
  443. end;
  444. procedure TIdCmdTCPServer.CheckOkToBeActive;
  445. begin
  446. if (CommandHandlers.Count = 0) and FCommandHandlersInitialized then begin
  447. inherited CheckOkToBeActive;
  448. end;
  449. end;
  450. end.