lwebserver.pp 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254
  1. { Web server component, built on the HTTP server component
  2. Copyright (C) 2006-2007 Micha Nelissen
  3. This library is Free software; you can redistribute it and/or modify it
  4. under the terms of the GNU Library General Public License as published by
  5. the Free Software Foundation; either version 2 of the License, or (at your
  6. option) any later version.
  7. This program is diStributed in the hope that it will be useful, but WITHOUT
  8. ANY WARRANTY; withOut even the implied warranty of MERCHANTABILITY or
  9. FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License
  10. for more details.
  11. You should have received a Copy of the GNU Library General Public License
  12. along with This library; if not, Write to the Free Software Foundation,
  13. Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  14. This license has been modified. See file LICENSE.ADDON for more information.
  15. Should you find these sources without a LICENSE File, please contact
  16. me at [email protected]
  17. }
  18. unit lwebserver;
  19. {$mode objfpc}{$h+}
  20. {$inline on}
  21. interface
  22. uses
  23. sysutils, classes, lhttp, lhttputil, lmimetypes, levents,
  24. lprocess, process, lfastcgi, fastcgi;
  25. type
  26. TLMultipartParameter = (mpContentType, mpContentDisposition, mpContentTransferEncoding,
  27. mpContentID, mpContentDescription);
  28. TLMultipartState = (msStart, msBodypartHeader, msBodypartData);
  29. const
  30. URIParamSepChar: char = '&';
  31. CookieSepChar: char = ';';
  32. FormURLContentType: pchar = 'application/x-www-form-urlencoded';
  33. MultipartContentType: pchar = 'multipart/form-data';
  34. MPParameterStrings: array[TLMultipartParameter] of string =
  35. ('Content-Type', 'Content-Disposition', 'Content-Transfer-Encoding',
  36. 'Content-ID', 'Content-Discription');
  37. type
  38. TDocumentHandler = class;
  39. TFileHandler = class;
  40. TFileOutput = class(TBufferOutput)
  41. protected
  42. FFile: file;
  43. function GetSize: integer;
  44. function FillBuffer: TWriteBlockStatus; override;
  45. public
  46. constructor Create(ASocket: TLHTTPSocket);
  47. destructor Destroy; override;
  48. function Open(const AFileName: string): boolean;
  49. property Size: integer read GetSize;
  50. end;
  51. TCGIOutput = class(TBufferOutput)
  52. protected
  53. FParsePos: pchar;
  54. FReadPos: integer;
  55. FParsingHeaders: boolean;
  56. procedure AddEnvironment(const AName, AValue: string); virtual; abstract;
  57. procedure AddHTTPParam(const AName: string; AParam: TLHTTPParameter);
  58. function ParseHeaders: boolean;
  59. procedure CGIOutputError; virtual; abstract;
  60. procedure WriteCGIBlock;
  61. function WriteCGIData: TWriteBlockStatus; virtual; abstract;
  62. public
  63. FDocumentRoot: string;
  64. FExtraPath: string;
  65. FEnvPath: string;
  66. FScriptFileName: string;
  67. FScriptName: string;
  68. constructor Create(ASocket: TLHTTPSocket);
  69. destructor Destroy; override;
  70. function FillBuffer: TWriteBlockStatus; override;
  71. procedure StartRequest; virtual;
  72. end;
  73. TSimpleCGIOutput = class(TCGIOutput)
  74. protected
  75. FProcess: TLProcess;
  76. procedure AddEnvironment(const AName, AValue: string); override;
  77. procedure CGIProcNeedInput(AHandle: TLHandle);
  78. procedure CGIProcHasOutput(AHandle: TLHandle);
  79. procedure CGIProcHasStderr(AHandle: TLHandle);
  80. procedure DoneInput; override;
  81. function HandleInput(ABuffer: pchar; ASize: integer): integer; override;
  82. procedure CGIOutputError; override;
  83. function WriteCGIData: TWriteBlockStatus; override;
  84. public
  85. constructor Create(ASocket: TLHTTPSocket);
  86. destructor Destroy; override;
  87. procedure StartRequest; override;
  88. property Process: TLProcess read FProcess;
  89. end;
  90. TFastCGIOutput = class(TCGIOutput)
  91. protected
  92. FRequest: TLFastCGIRequest;
  93. procedure AddEnvironment(const AName, AValue: string); override;
  94. procedure CGIOutputError; override;
  95. procedure DoneInput; override;
  96. procedure RequestEnd(ARequest: TLFastCGIRequest);
  97. procedure RequestNeedInput(ARequest: TLFastCGIRequest);
  98. procedure RequestHasOutput(ARequest: TLFastCGIRequest);
  99. procedure RequestHasStderr(ARequest: TLFastCGIRequest);
  100. function HandleInput(ABuffer: pchar; ASize: integer): integer; override;
  101. function WriteCGIData: TWriteBlockStatus; override;
  102. function WriteBlock: TWriteBlockStatus; override;
  103. public
  104. constructor Create(ASocket: TLHTTPSocket);
  105. destructor Destroy; override;
  106. procedure StartRequest; override;
  107. property Request: TLFastCGIRequest read FRequest write FRequest;
  108. end;
  109. TCGIHandler = class(TURIHandler)
  110. protected
  111. function HandleURI(ASocket: TLHTTPServerSocket): TOutputItem; override;
  112. public
  113. FCGIRoot: string;
  114. FEnvPath: string;
  115. FDocumentRoot: string;
  116. FScriptPathPrefix: string;
  117. end;
  118. TDocumentRequest = record
  119. Socket: TLHTTPServerSocket;
  120. Document: string;
  121. URIPath: string;
  122. ExtraPath: string;
  123. Info: TSearchRec;
  124. InfoValid: boolean;
  125. end;
  126. TDocumentHandler = class(TObject)
  127. private
  128. FNext: TDocumentHandler;
  129. protected
  130. FFileHandler: TFileHandler;
  131. procedure RegisterWithEventer(AEventer: TLEventer); virtual;
  132. public
  133. function HandleDocument(const ARequest: TDocumentRequest): TOutputItem; virtual; abstract;
  134. property FileHandler: TFileHandler read FFileHandler;
  135. end;
  136. { TFileHandler }
  137. TFileHandler = class(TURIHandler)
  138. protected
  139. FDocHandlerList: TDocumentHandler;
  140. FDirIndexList: TStrings;
  141. FMimeTypeFile: string;
  142. procedure SetMimeTypeFile(const AValue: string);
  143. function HandleFile(const ARequest: TDocumentRequest): TOutputItem;
  144. function HandleURI(ASocket: TLHTTPServerSocket): TOutputItem; override;
  145. procedure RegisterWithEventer(AEventer: TLEventer); override;
  146. public
  147. DocumentRoot: string;
  148. constructor Create;
  149. destructor Destroy; override;
  150. procedure RegisterHandler(AHandler: TDocumentHandler);
  151. property DirIndexList: TStrings read FDirIndexList;
  152. property MimeTypeFile: string read FMimeTypeFile write SetMimeTypeFile;
  153. end;
  154. TPHPCGIHandler = class(TDocumentHandler)
  155. protected
  156. FAppName: string;
  157. FEnvPath: string;
  158. public
  159. function HandleDocument(const ARequest: TDocumentRequest): TOutputItem; override;
  160. property AppName: string read FAppName write FAppName;
  161. property EnvPath: string read FEnvPath write FEnvPath;
  162. end;
  163. TPHPFastCGIHandler = class(TDocumentHandler)
  164. protected
  165. FPool: TLFastCGIPool;
  166. FEnvPath: string;
  167. function GetAppEnv: string;
  168. function GetAppName: string;
  169. function GetHost: string;
  170. function GetPort: integer;
  171. procedure RegisterWithEventer(AEventer: TLEventer); override;
  172. procedure SetAppEnv(NewEnv: string);
  173. procedure SetAppName(NewName: string);
  174. procedure SetHost(NewHost: string);
  175. procedure SetPort(NewPort: integer);
  176. public
  177. constructor Create;
  178. destructor Destroy; override;
  179. function HandleDocument(const ARequest: TDocumentRequest): TOutputItem; override;
  180. property AppEnv: string read GetAppEnv write SetAppEnv;
  181. property AppName: string read GetAppName write SetAppName;
  182. property EnvPath: string read FEnvPath write FEnvPath;
  183. property Host: string read GetHost write SetHost;
  184. property Pool: TLFastCGIPool read FPool;
  185. property Port: integer read GetPort write SetPort;
  186. end;
  187. { Forms }
  188. TFormOutput = class;
  189. TFillBufferEvent = procedure(AFormOutput: TFormOutput; var AStatus: TWriteBlockStatus);
  190. THandleInputMethod = function(ABuffer: pchar; ASize: integer): integer of object;
  191. TFormOutput = class(TBufferOutput)
  192. protected
  193. FBoundary: pchar;
  194. FRequestVars: TStrings;
  195. FMPParameters: array[TLMultipartParameter] of pchar;
  196. FMPState: TLMultipartState;
  197. FOnExtraHeaders: TNotifyEvent;
  198. FOnFillBuffer: TFillBufferEvent;
  199. FHandleInput: THandleInputMethod;
  200. procedure DoneInput; override;
  201. function FillBuffer: TWriteBlockStatus; override;
  202. function FindBoundary(ABuffer: pchar): pchar;
  203. function HandleInput(ABuffer: pchar; ASize: integer): integer; override;
  204. function HandleInputDiscard(ABuffer: pchar; ASize: integer): integer;
  205. function HandleInputFormURL(ABuffer: pchar; ASize: integer): integer;
  206. function HandleInputMultipart(ABuffer: pchar; ASize: integer): integer;
  207. procedure ParseMultipartHeader(ABuffer, ALineEnd: pchar);
  208. public
  209. constructor Create(ASocket: TLHTTPSocket);
  210. destructor Destroy; override;
  211. function AddVariables(Variables: pchar; ASize: integer; SepChar: char): integer;
  212. procedure DeleteCookie(const AName: string; const APath: string = '/';
  213. const ADomain: string = '');
  214. procedure SetCookie(const AName, AValue: string; const AExpires: TDateTime;
  215. const APath: string = '/'; const ADomain: string = '');
  216. property OnExtraHeaders: TNotifyEvent read FOnExtraHeaders write FOnExtraHeaders;
  217. property OnFillBuffer: TFillBufferEvent read FOnFillBuffer write FOnFillBuffer;
  218. end;
  219. THandleURIEvent = function(ASocket: TLHTTPServerSocket): TFormOutput;
  220. TFormHandler = class(TURIHandler)
  221. protected
  222. FOnHandleURI: THandleURIEvent;
  223. function HandleURI(ASocket: TLHTTPServerSocket): TOutputItem; override;
  224. procedure SelectMultipart(AFormOutput: TFormOutput; AContentType: pchar);
  225. public
  226. property OnHandleURI: THandleURIEvent read FOnHandleURI write FOnHandleURI;
  227. end;
  228. var
  229. EnableWriteln: Boolean = True;
  230. implementation
  231. uses
  232. lstrbuffer;
  233. { Example handlers }
  234. const
  235. InputBufferEmptyToWriteStatus: array[boolean] of TWriteBlockStatus =
  236. (wsPendingData, wsWaitingData);
  237. procedure InternalWrite(const s: string);
  238. begin
  239. if EnableWriteln then
  240. Writeln(s);
  241. end;
  242. procedure TDocumentHandler.RegisterWithEventer(AEventer: TLEventer);
  243. begin
  244. end;
  245. function TCGIHandler.HandleURI(ASocket: TLHTTPServerSocket): TOutputItem;
  246. var
  247. lOutput: TSimpleCGIOutput;
  248. lExecPath: string;
  249. begin
  250. if StrLComp(ASocket.FRequestInfo.Argument, PChar(FScriptPathPrefix),
  251. Length(FScriptPathPrefix)) = 0 then
  252. begin
  253. lOutput := TSimpleCGIOutput.Create(ASocket);
  254. lOutput.FDocumentRoot := FDocumentRoot;
  255. lOutput.FEnvPath := FEnvPath;
  256. lOutput.Process.CurrentDirectory := FCGIRoot;
  257. lExecPath := ASocket.FRequestInfo.Argument+Length(FScriptPathPrefix);
  258. DoDirSeparators(lExecPath);
  259. lExecPath := FCGIRoot+lExecPath;
  260. if SeparatePath(lExecPath, lOutput.FExtraPath, faAnyFile and not faDirectory) then
  261. begin
  262. lOutput.Process.CommandLine := lExecPath;
  263. lOutput.FScriptFileName := lExecPath;
  264. lOutput.FScriptName := Copy(lExecPath, Length(FCGIRoot),
  265. Length(lExecPath)-Length(FCGIRoot)+1);
  266. lOutput.StartRequest;
  267. end else
  268. ASocket.FResponseInfo.Status := hsNotFound;
  269. Result := lOutput;
  270. end else
  271. Result := nil;
  272. end;
  273. constructor TFileHandler.Create;
  274. begin
  275. inherited;
  276. FDirIndexList := TStringList.Create;
  277. end;
  278. destructor TFileHandler.Destroy;
  279. begin
  280. FreeAndNil(FDirIndexList);
  281. inherited;
  282. end;
  283. procedure TFileHandler.RegisterWithEventer(AEventer: TLEventer);
  284. var
  285. lHandler: TDocumentHandler;
  286. begin
  287. lHandler := FDocHandlerList;
  288. while lHandler <> nil do
  289. begin
  290. lHandler.RegisterWithEventer(AEventer);
  291. lHandler := lHandler.FNext;
  292. end;
  293. end;
  294. procedure TFileHandler.SetMimeTypeFile(const AValue: string);
  295. begin
  296. FMimeTypeFile:=AValue;
  297. InitMimeList(aValue);
  298. end;
  299. function TFileHandler.HandleFile(const ARequest: TDocumentRequest): TOutputItem;
  300. var
  301. lFileOutput: TFileOutput;
  302. lReqInfo: PRequestInfo;
  303. lRespInfo: PResponseInfo;
  304. lHeaderOut: PHeaderOutInfo;
  305. lIndex: integer;
  306. begin
  307. Result := nil;
  308. if ARequest.InfoValid then
  309. begin
  310. lReqInfo := @ARequest.Socket.FRequestInfo;
  311. lRespInfo := @ARequest.Socket.FResponseInfo;
  312. lHeaderOut := @ARequest.Socket.FHeaderOut;
  313. if not (lReqInfo^.RequestType in [hmHead, hmGet]) then
  314. begin
  315. lRespInfo^.Status := hsNotAllowed;
  316. end else begin
  317. lFileOutput := TFileOutput.Create(ARequest.Socket);
  318. if lFileOutput.Open(ARequest.Document) then
  319. begin
  320. lRespInfo^.Status := hsOK;
  321. lHeaderOut^.ContentLength := ARequest.Info.Size;
  322. lRespInfo^.LastModified := LocalTimeToGMT(FileDateToDateTime(ARequest.Info.Time));
  323. lIndex := MimeList.IndexOf(ExtractFileExt(ARequest.Document));
  324. if lIndex >= 0 then
  325. lRespInfo^.ContentType := TStringObject(MimeList.Objects[lIndex]).Str;
  326. Result := lFileOutput;
  327. ARequest.Socket.StartResponse(lFileOutput);
  328. end else
  329. lFileOutput.Free;
  330. end;
  331. end;
  332. end;
  333. function TFileHandler.HandleURI(ASocket: TLHTTPServerSocket): TOutputItem;
  334. var
  335. lDocRequest: TDocumentRequest;
  336. lHandler: TDocumentHandler;
  337. lTempDoc: string;
  338. lDirIndexFound: boolean;
  339. I: integer;
  340. begin
  341. Result := nil;
  342. lDocRequest.Socket := ASocket;
  343. lDocRequest.URIPath := ASocket.FRequestInfo.Argument;
  344. lDocRequest.Document := lDocRequest.URIPath;
  345. DoDirSeparators(LDocRequest.Document);
  346. lDocRequest.Document := IncludeTrailingPathDelimiter(DocumentRoot)
  347. + lDocRequest.Document;
  348. lDocRequest.InfoValid := SeparatePath(lDocRequest.Document,lDocRequest.ExtraPath,
  349. faAnyFile, @lDocRequest.Info);
  350. if not lDocRequest.InfoValid then
  351. exit;
  352. if (lDocRequest.Info.Attr and faDirectory) <> 0 then
  353. begin
  354. lDirIndexFound := false;
  355. { if non-trivial ExtraPath, then it's not a pure directory request, so do
  356. not show default directory document }
  357. if lDocRequest.ExtraPath = PathDelim then
  358. begin
  359. lDocRequest.Document := IncludeTrailingPathDelimiter(lDocRequest.Document);
  360. for I := 0 to FDirIndexList.Count - 1 do
  361. begin
  362. lTempDoc := lDocRequest.Document + FDirIndexList.Strings[I];
  363. lDocRequest.InfoValid := FindFirst(lTempDoc,
  364. faAnyFile and not faDirectory, lDocRequest.Info) = 0;
  365. FindClose(lDocRequest.Info);
  366. if lDocRequest.InfoValid and ((lDocRequest.Info.Attr and faDirectory) = 0) then
  367. begin
  368. lDocRequest.Document := lTempDoc;
  369. lDirIndexFound := true;
  370. break;
  371. end;
  372. end;
  373. end;
  374. { requested a directory, but no source to show }
  375. if not lDirIndexFound then exit;
  376. end;
  377. lHandler := FDocHandlerList;
  378. while lHandler <> nil do
  379. begin
  380. Result := lHandler.HandleDocument(lDocRequest);
  381. if Result <> nil then exit;
  382. if ASocket.FResponseInfo.Status <> hsOK then exit;
  383. lHandler := lHandler.FNext;
  384. end;
  385. { no dynamic handler, see if it's a plain file }
  386. Result := HandleFile(lDocRequest);
  387. end;
  388. procedure TFileHandler.RegisterHandler(AHandler: TDocumentHandler);
  389. begin
  390. if AHandler = nil then exit;
  391. AHandler.FFileHandler := Self;
  392. AHandler.FNext := FDocHandlerList;
  393. FDocHandlerList := AHandler;
  394. end;
  395. function TPHPCGIHandler.HandleDocument(const ARequest: TDocumentRequest): TOutputItem;
  396. var
  397. lOutput: TSimpleCGIOutput;
  398. begin
  399. if ExtractFileExt(ARequest.Document) = '.php' then
  400. begin
  401. lOutput := TSimpleCGIOutput.Create(ARequest.Socket);
  402. lOutput.FDocumentRoot := FFileHandler.DocumentRoot;
  403. lOutput.Process.CommandLine := FAppName;
  404. lOutput.FScriptName := ARequest.URIPath;
  405. lOutput.FScriptFileName := ARequest.Document;
  406. lOutput.FExtraPath := ARequest.ExtraPath;
  407. lOutput.FEnvPath := FEnvPath;
  408. lOutput.StartRequest;
  409. Result := lOutput;
  410. end else
  411. Result := nil;
  412. end;
  413. constructor TPHPFastCGIHandler.Create;
  414. begin
  415. inherited;
  416. FPool := TLFastCGIPool.Create;
  417. end;
  418. destructor TPHPFastCGIHandler.Destroy;
  419. begin
  420. inherited;
  421. FPool.Free;
  422. end;
  423. function TPHPFastCGIHandler.GetAppEnv: string;
  424. begin
  425. Result := FPool.AppEnv;
  426. end;
  427. function TPHPFastCGIHandler.GetAppName: string;
  428. begin
  429. Result := FPool.AppName;
  430. end;
  431. function TPHPFastCGIHandler.GetHost: string;
  432. begin
  433. Result := FPool.Host;
  434. end;
  435. function TPHPFastCGIHandler.GetPort: integer;
  436. begin
  437. Result := FPool.Port;
  438. end;
  439. procedure TPHPFastCGIHandler.SetAppEnv(NewEnv: string);
  440. begin
  441. FPool.AppEnv := NewEnv;
  442. end;
  443. procedure TPHPFastCGIHandler.SetAppName(NewName: string);
  444. begin
  445. FPool.AppName := NewName;
  446. end;
  447. procedure TPHPFastCGIHandler.SetHost(NewHost: string);
  448. begin
  449. FPool.Host := NewHost;
  450. end;
  451. procedure TPHPFastCGIHandler.SetPort(NewPort: integer);
  452. begin
  453. FPool.Port := NewPort;
  454. end;
  455. procedure TPHPFastCGIHandler.RegisterWithEventer(AEventer: TLEventer);
  456. begin
  457. FPool.Eventer := AEventer;
  458. end;
  459. function TPHPFastCGIHandler.HandleDocument(const ARequest: TDocumentRequest): TOutputItem;
  460. var
  461. lOutput: TFastCGIOutput;
  462. fcgiRequest: TLFastCGIRequest;
  463. begin
  464. if ExtractFileExt(ARequest.Document) = '.php' then
  465. begin
  466. fcgiRequest := FPool.BeginRequest(FCGI_RESPONDER);
  467. if fcgiRequest <> nil then
  468. begin
  469. lOutput := TFastCGIOutput.Create(ARequest.Socket);
  470. lOutput.FDocumentRoot := FFileHandler.DocumentRoot;
  471. lOutput.FScriptName := ARequest.URIPath;
  472. lOutput.FScriptFileName := ARequest.Document;
  473. lOutput.FExtraPath := ARequest.ExtraPath;
  474. lOutput.FEnvPath := FEnvPath;
  475. lOutput.Request := fcgiRequest;
  476. ARequest.Socket.SetupEncoding(lOutput);
  477. lOutput.StartRequest;
  478. Result := lOutput;
  479. end else begin
  480. ARequest.Socket.FResponseInfo.Status := hsInternalError;
  481. ARequest.Socket.StartResponse(nil);
  482. Result := nil;
  483. end;
  484. end else
  485. Result := nil;
  486. end;
  487. { Output Items }
  488. constructor TFileOutput.Create(ASocket: TLHTTPSocket);
  489. begin
  490. inherited;
  491. FEof := true;
  492. end;
  493. destructor TFileOutput.Destroy;
  494. begin
  495. inherited;
  496. if not FEof then
  497. Close(FFile);
  498. end;
  499. function TFileOutput.Open(const AFileName: string): boolean;
  500. begin
  501. {$I-}
  502. FileMode := 0;
  503. Assign(FFile, AFileName);
  504. Reset(FFile,1);
  505. {$I+}
  506. Result := IOResult = 0;
  507. FEof := false;
  508. end;
  509. function TFileOutput.GetSize: integer; inline;
  510. begin
  511. Result := FileSize(FFile);
  512. end;
  513. function TFileOutput.FillBuffer: TWriteBlockStatus;
  514. var
  515. lRead: integer;
  516. begin
  517. if FEof then
  518. exit(wsDone);
  519. BlockRead(FFile, FBuffer[FBufferPos], FBufferSize-FBufferPos, lRead);
  520. Inc(FBufferPos, lRead);
  521. if lRead = 0 then
  522. begin
  523. { EOF reached }
  524. Close(FFile);
  525. exit(wsDone);
  526. end;
  527. Result := wsPendingData;
  528. end;
  529. constructor TCGIOutput.Create(ASocket: TLHTTPSocket);
  530. begin
  531. inherited;
  532. end;
  533. destructor TCGIOutput.Destroy;
  534. begin
  535. inherited;
  536. end;
  537. procedure TCGIOutput.AddHTTPParam(const AName: string; AParam: TLHTTPParameter);
  538. var
  539. lValue: pchar;
  540. begin
  541. lValue := FSocket.Parameters[AParam];
  542. if lValue = nil then exit;
  543. AddEnvironment(AName, lValue);
  544. end;
  545. procedure TCGIOutput.StartRequest;
  546. var
  547. lServerSocket: TLHTTPServerSocket absolute FSocket;
  548. tempStr: string;
  549. begin
  550. {
  551. FProcess.Environment.Add('SERVER_ADDR=');
  552. FProcess.Environment.Add('SERVER_ADMIN=');
  553. FProcess.Environment.Add('SERVER_NAME=');
  554. FProcess.Environment.Add('SERVER_PORT=');
  555. }
  556. tempStr := TLHTTPServer(lServerSocket.Creator).ServerSoftware;
  557. if Length(tempStr) > 0 then
  558. AddEnvironment('SERVER_SOFTWARE', tempStr);
  559. AddEnvironment('GATEWAY_INTERFACE', 'CGI/1.1');
  560. AddEnvironment('SERVER_PROTOCOL', lServerSocket.FRequestInfo.VersionStr);
  561. AddEnvironment('REQUEST_METHOD', lServerSocket.FRequestInfo.Method);
  562. AddEnvironment('REQUEST_URI', '/'+lServerSocket.FRequestInfo.Argument);
  563. if Length(FExtraPath) > 0 then
  564. begin
  565. AddEnvironment('PATH_INFO', FExtraPath);
  566. { do not set PATH_TRANSLATED: bug in PHP }
  567. // AddEnvironment('PATH_TRANSLATED', DocumentRoot+FExtraPath);
  568. end;
  569. AddEnvironment('SCRIPT_NAME', FScriptName);
  570. AddEnvironment('SCRIPT_FILENAME', FScriptFileName);
  571. AddEnvironment('QUERY_STRING', lServerSocket.FRequestInfo.QueryParams);
  572. AddHTTPParam('CONTENT_TYPE', hpContentType);
  573. AddHTTPParam('CONTENT_LENGTH', hpContentLength);
  574. AddEnvironment('REMOTE_ADDR', FSocket.PeerAddress);
  575. AddEnvironment('REMOTE_PORT', IntToStr(FSocket.LocalPort));
  576. { used when user has authenticated in some way to server }
  577. // AddEnvironment('AUTH_TYPE='+...);
  578. // AddEnvironment('REMOTE_USER='+...);
  579. AddEnvironment('DOCUMENT_ROOT', FDocumentRoot);
  580. AddEnvironment('REDIRECT_STATUS', '200');
  581. AddHTTPParam('HTTP_HOST', hpHost);
  582. AddHTTPParam('HTTP_COOKIE', hpCookie);
  583. AddHTTPParam('HTTP_CONNECTION', hpConnection);
  584. AddHTTPParam('HTTP_REFERER', hpReferer);
  585. AddHTTPParam('HTTP_USER_AGENT', hpUserAgent);
  586. AddHTTPParam('HTTP_ACCEPT', hpAccept);
  587. AddEnvironment('PATH', FEnvPath);
  588. FParsingHeaders := true;
  589. FReadPos := FBufferPos;
  590. FParsePos := FBuffer+FReadPos;
  591. end;
  592. function TCGIOutput.ParseHeaders: boolean;
  593. var
  594. lHttpStatus: TLHTTPStatus;
  595. iEnd, lCode: integer;
  596. lStatus, lLength: dword;
  597. pLineEnd, pNextLine, pValue: pchar;
  598. lServerSocket: TLHTTPServerSocket absolute FSocket;
  599. procedure AddExtraHeader;
  600. begin
  601. AppendString(lServerSocket.FHeaderOut.ExtraHeaders,
  602. FParsePos + ': ' + pValue + #13#10);
  603. end;
  604. begin
  605. repeat
  606. iEnd := IndexByte(FParsePos^, @FBuffer[FReadPos]-FParsePos, 10);
  607. if iEnd = -1 then exit(false);
  608. pNextLine := FParsePos+iEnd+1;
  609. if (iEnd > 0) and (FParsePos[iEnd-1] = #13) then
  610. dec(iEnd);
  611. pLineEnd := FParsePos+iEnd;
  612. pLineEnd^ := #0;
  613. if pLineEnd = FParsePos then
  614. begin
  615. { empty line signals end of headers }
  616. FParsingHeaders := false;
  617. FBufferOffset := pNextLine-FBuffer;
  618. FBufferPos := FReadPos;
  619. FReadPos := 0;
  620. lServerSocket.StartResponse(Self, true);
  621. exit(false);
  622. end;
  623. iEnd := IndexByte(FParsePos^, iEnd, ord(':'));
  624. if (iEnd = -1) or (FParsePos[iEnd+1] <> ' ') then
  625. break;
  626. FParsePos[iEnd] := #0;
  627. pValue := FParsePos+iEnd+2;
  628. if StrIComp(FParsePos, 'Content-type') = 0 then
  629. begin
  630. lServerSocket.FResponseInfo.ContentType := pValue;
  631. end else
  632. if StrIComp(FParsePos, 'Location') = 0 then
  633. begin
  634. if StrLIComp(pValue, 'http://', 7) = 0 then
  635. begin
  636. lServerSocket.FResponseInfo.Status := hsMovedPermanently;
  637. { add location header as-is to response }
  638. AddExtraHeader;
  639. end else
  640. InternalWrite('WARNING: unimplemented ''Location'' response received from CGI script');
  641. end else
  642. if StrIComp(FParsePos, 'Status') = 0 then
  643. begin
  644. { sometimes we get '<status code> space <reason>' }
  645. iEnd := IndexByte(pValue^, pLineEnd-pValue, ord(' '));
  646. if iEnd <> -1 then
  647. pValue[iEnd] := #0;
  648. Val(pValue, lStatus, lCode);
  649. if lCode <> 0 then
  650. break;
  651. for lHttpStatus := Low(TLHTTPStatus) to High(TLHTTPStatus) do
  652. if HTTPStatusCodes[lHttpStatus] = lStatus then
  653. lServerSocket.FResponseInfo.Status := lHttpStatus;
  654. end else
  655. if StrIComp(FParsePos, 'Content-Length') = 0 then
  656. begin
  657. Val(pValue, lLength, lCode);
  658. if lCode <> 0 then
  659. break;
  660. lServerSocket.FHeaderOut.ContentLength := lLength;
  661. end else
  662. if StrIComp(FParsePos, 'Last-Modified') = 0 then
  663. begin
  664. if not TryHTTPDateStrToDateTime(pValue,
  665. lServerSocket.FResponseInfo.LastModified) then
  666. InternalWrite('WARNING: unable to parse last-modified string from CGI script: ' + pValue);
  667. end else
  668. AddExtraHeader;
  669. FParsePos := pNextLine;
  670. until false;
  671. { error happened }
  672. lServerSocket.FResponseInfo.Status := hsInternalError;
  673. exit(true);
  674. end;
  675. function TCGIOutput.FillBuffer: TWriteBlockStatus;
  676. begin
  677. if not FParsingHeaders then
  678. FReadPos := FBufferPos;
  679. Result := WriteCGIData;
  680. if FParsingHeaders then
  681. begin
  682. if ParseHeaders then
  683. begin
  684. { error while parsing }
  685. FEof := true;
  686. exit(wsDone);
  687. end;
  688. end else
  689. FBufferPos := FReadPos;
  690. end;
  691. procedure TCGIOutput.WriteCGIBlock;
  692. begin
  693. { CGI process has output pending, we can write a block to socket }
  694. if FParsingHeaders then
  695. begin
  696. if (FillBuffer = wsDone) and FParsingHeaders then
  697. begin
  698. { still parsing headers ? something's wrong }
  699. FParsingHeaders := false;
  700. CGIOutputError;
  701. TLHTTPServerSocket(FSocket).StartResponse(Self);
  702. end;
  703. end;
  704. if not FParsingHeaders then
  705. FSocket.WriteBlock;
  706. end;
  707. { TSimpleCGIOutput }
  708. constructor TSimpleCGIOutput.Create(ASocket: TLHTTPSocket);
  709. begin
  710. inherited;
  711. FProcess := TLProcess.Create(nil);
  712. FProcess.Options := FProcess.Options + [poUsePipes];
  713. FProcess.OnNeedInput := @CGIProcNeedInput;
  714. FProcess.OnHasOutput := @CGIProcHasOutput;
  715. FProcess.OnHasStderr := @CGIProcHasStderr;
  716. end;
  717. destructor TSimpleCGIOutput.Destroy;
  718. begin
  719. inherited;
  720. FProcess.Free;
  721. end;
  722. function TSimpleCGIOutput.WriteCGIData: TWriteBlockStatus;
  723. var
  724. lRead: integer;
  725. begin
  726. lRead := FProcess.Output.Read(FBuffer[FReadPos], FBufferSize-FReadPos);
  727. if lRead = 0 then exit(wsDone);
  728. Inc(FReadPos, lRead);
  729. Result := InputBufferEmptyToWriteStatus[lRead = 0];
  730. end;
  731. procedure TSimpleCGIOutput.AddEnvironment(const AName, AValue: string);
  732. begin
  733. FProcess.Environment.Add(AName+'='+AValue);
  734. end;
  735. procedure TSimpleCGIOutput.DoneInput;
  736. begin
  737. FProcess.CloseInput;
  738. end;
  739. function TSimpleCGIOutput.HandleInput(ABuffer: pchar; ASize: integer): integer;
  740. begin
  741. if ASize > 0 then
  742. Result := FProcess.Input.Write(ABuffer^, ASize)
  743. else
  744. Result := 0;
  745. FProcess.InputEvent.IgnoreWrite := ASize = 0;
  746. end;
  747. procedure TSimpleCGIOutput.StartRequest;
  748. begin
  749. inherited;
  750. FProcess.Eventer := FSocket.Eventer;
  751. FProcess.Execute;
  752. end;
  753. procedure TSimpleCGIOutput.CGIOutputError;
  754. var
  755. ServerSocket: TLHTTPServerSocket absolute FSocket;
  756. begin
  757. if FProcess.ExitStatus = 127 then
  758. ServerSocket.FResponseInfo.Status := hsNotFound
  759. else
  760. ServerSocket.FResponseInfo.Status := hsInternalError;
  761. end;
  762. procedure TSimpleCGIOutput.CGIProcNeedInput(AHandle: TLHandle);
  763. begin
  764. FProcess.InputEvent.IgnoreWrite := true;
  765. FSocket.ParseBuffer;
  766. end;
  767. procedure TSimpleCGIOutput.CGIProcHasOutput(AHandle: TLHandle);
  768. begin
  769. WriteCGIBlock;
  770. end;
  771. procedure TSimpleCGIOutput.CGIProcHasStderr(AHandle: TLHandle);
  772. var
  773. lBuf: array[0..1023] of char;
  774. lRead: integer;
  775. begin
  776. lRead := FProcess.Stderr.Read(lBuf, sizeof(lBuf)-1);
  777. lBuf[lRead] := #0;
  778. write(pchar(@lBuf[0]));
  779. end;
  780. { TFastCGIOutput }
  781. constructor TFastCGIOutput.Create(ASocket: TLHTTPSocket);
  782. begin
  783. inherited;
  784. end;
  785. destructor TFastCGIOutput.Destroy;
  786. begin
  787. if FRequest <> nil then
  788. begin
  789. FRequest.OnInput := nil;
  790. FRequest.OnOutput := nil;
  791. FRequest.OnStderr := nil;
  792. FRequest.OnEndRequest := nil;
  793. FRequest.AbortRequest;
  794. end;
  795. inherited;
  796. end;
  797. procedure TFastCGIOutput.AddEnvironment(const AName, AValue: string);
  798. begin
  799. FRequest.SendParam(AName, AValue);
  800. end;
  801. procedure TFastCGIOutput.CGIOutputError;
  802. begin
  803. TLHTTPServerSocket(FSocket).FResponseInfo.Status := hsInternalError;
  804. end;
  805. procedure TFastCGIOutput.DoneInput;
  806. begin
  807. if FRequest <> nil then
  808. FRequest.DoneInput;
  809. end;
  810. procedure TFastCGIOutput.RequestEnd(ARequest: TLFastCGIRequest);
  811. begin
  812. FRequest.OnEndRequest := nil;
  813. FRequest.OnInput := nil;
  814. FRequest.OnOutput := nil;
  815. FRequest := nil;
  816. { trigger final write, to flush output to socket }
  817. WriteCGIBlock;
  818. end;
  819. procedure TFastCGIOutput.RequestNeedInput(ARequest: TLFastCGIRequest);
  820. begin
  821. FSocket.ParseBuffer;
  822. end;
  823. procedure TFastCGIOutput.RequestHasOutput(ARequest: TLFastCGIRequest);
  824. begin
  825. WriteCGIBlock;
  826. end;
  827. procedure TFastCGIOutput.RequestHasStderr(ARequest: TLFastCGIRequest);
  828. var
  829. lBuf: array[0..1023] of char;
  830. lRead: integer;
  831. begin
  832. lRead := ARequest.Get(lBuf, sizeof(lBuf)-1);
  833. lBuf[lRead] := #0;
  834. write(pchar(@lBuf[0]));
  835. end;
  836. function TFastCGIOutput.HandleInput(ABuffer: pchar; ASize: integer): integer;
  837. begin
  838. Result := FRequest.SendInput(ABuffer, ASize);
  839. end;
  840. function TFastCGIOutput.WriteCGIData: TWriteBlockStatus;
  841. var
  842. lRead: integer;
  843. begin
  844. if FRequest = nil then exit(wsDone);
  845. if FRequest.OutputDone then exit(wsDone);
  846. lRead := FRequest.Get(@FBuffer[FReadPos], FBufferSize-FReadPos);
  847. Inc(FReadPos, lRead);
  848. Result := InputBufferEmptyToWriteStatus[lRead = 0];
  849. end;
  850. function TFastCGIOutput.WriteBlock: TWriteBlockStatus;
  851. begin
  852. if (FRequest <> nil) and FRequest.OutputPending then
  853. begin
  854. FRequest.ParseClientBuffer;
  855. Result := wsWaitingData;
  856. end else
  857. Result := inherited;
  858. end;
  859. procedure TFastCGIOutput.StartRequest;
  860. begin
  861. FRequest.OnEndRequest := @RequestEnd;
  862. FRequest.OnInput := @RequestNeedInput;
  863. FRequest.OnOutput := @RequestHasOutput;
  864. FRequest.OnStderr := @RequestHasStderr;
  865. inherited;
  866. FRequest.DoneParams;
  867. end;
  868. { TFormOutput }
  869. constructor TFormOutput.Create(ASocket: TLHTTPSocket);
  870. begin
  871. inherited;
  872. FRequestVars := TStringList.Create;
  873. end;
  874. destructor TFormOutput.Destroy;
  875. var
  876. I: integer;
  877. tmpObj: TObject;
  878. begin
  879. for I := 0 to FRequestVars.Count - 1 do
  880. begin
  881. tmpObj := FRequestVars.Objects[I];
  882. Finalize(string(tmpObj));
  883. FRequestVars.Objects[I] := nil;
  884. end;
  885. FRequestVars.Free;
  886. inherited;
  887. end;
  888. function TFormOutput.AddVariables(Variables: pchar; ASize: integer; SepChar: char): integer;
  889. var
  890. varname, sep, next: pchar;
  891. strName, strValue: string;
  892. tmpObj: TObject;
  893. i: integer;
  894. begin
  895. if Variables = nil then
  896. exit(0);
  897. if ASize = -1 then
  898. ASize := StrLen(Variables);
  899. varname := Variables;
  900. repeat
  901. sep := varname + IndexChar(varname^, ASize, '=');
  902. if sep < varname then
  903. break;
  904. dec(ASize, sep-varname);
  905. next := sep + IndexChar(sep^, ASize, SepChar);
  906. if next < sep then
  907. begin
  908. next := sep + ASize;
  909. ASize := 0;
  910. end else
  911. dec(ASize, next+1-sep);
  912. if sep > varname then
  913. begin
  914. setlength(strName, sep-varname);
  915. move(varname[0], strName[1], sep-varname);
  916. setlength(strValue, next-sep-1);
  917. move(sep[1], strValue[1], next-sep-1);
  918. i := FRequestVars.Add(strName);
  919. tmpObj := nil;
  920. string(tmpObj) := strValue;
  921. FRequestVars.Objects[i] := tmpObj;
  922. end;
  923. varname := next+1;
  924. until false;
  925. Result := ASize;
  926. end;
  927. procedure TFormOutput.DoneInput;
  928. begin
  929. if Assigned(FOnExtraHeaders) then
  930. FOnExtraHeaders(Self);
  931. TLHTTPServerSocket(FSocket).StartResponse(Self);
  932. end;
  933. function TFormOutput.HandleInputFormURL(ABuffer: pchar; ASize: integer): integer;
  934. begin
  935. Result := ASize-AddVariables(ABuffer, ASize, URIParamSepChar)
  936. end;
  937. procedure TFormOutput.ParseMultipartHeader(ABuffer, ALineEnd: pchar);
  938. var
  939. I: TLMultipartParameter;
  940. len: integer;
  941. begin
  942. for I := Low(TLMultipartParameter) to High(TLMultipartParameter) do
  943. begin
  944. len := Length(MPParameterStrings[I]);
  945. if ABuffer+len >= ALineEnd then
  946. continue;
  947. if (ABuffer[len] = ':')
  948. and (StrLIComp(ABuffer, PChar(MPParameterStrings[I]), len) = 0) then
  949. begin
  950. Inc(ABuffer, len+2);
  951. repeat
  952. if ABuffer = ALineEnd then exit;
  953. if ABuffer^ <> ' ' then break;
  954. inc(ABuffer);
  955. until false;
  956. FMPParameters[I] := ABuffer;
  957. if I = mpContentType then
  958. begin
  959. repeat
  960. if ABuffer = ALineEnd then exit;
  961. if ABuffer = ';' then break;
  962. inc(ABuffer);
  963. until false;
  964. end;
  965. break;
  966. end;
  967. end;
  968. end;
  969. function TFormOutput.FindBoundary(ABuffer: pchar): pchar;
  970. begin
  971. {$warning TODO}
  972. Result := nil;
  973. end;
  974. function TFormOutput.HandleInputMultipart(ABuffer: pchar; ASize: integer): integer;
  975. var
  976. pos, next, endline: pchar;
  977. begin
  978. pos := ABuffer;
  979. repeat
  980. case FMPState of
  981. msStart:
  982. begin
  983. { discard until first boundary }
  984. next := FindBoundary(pos);
  985. if next = nil then
  986. exit(ASize);
  987. FMPState := msBodypartHeader;
  988. end;
  989. msBodypartHeader:
  990. begin
  991. endline := pos + IndexChar(pos, ASize, #10);
  992. if endline < pos then
  993. exit(pos-ABuffer);
  994. next := endline+1;
  995. if (endline > pos) and ((endline-1)^ = #13) then
  996. dec(endline);
  997. endline^ := #0;
  998. if endline > pos then
  999. ParseMultipartHeader(pos, endline)
  1000. else
  1001. FMPState := msBodypartData;
  1002. end;
  1003. msBodypartData:
  1004. begin
  1005. { decode based on content-transfer-encoding ? }
  1006. { CRLF before boundary, belongs to boundary, not data! }
  1007. next := FindBoundary(ABuffer);
  1008. end;
  1009. else
  1010. exit(ASize);
  1011. end;
  1012. dec(ASize, next-pos);
  1013. pos := next;
  1014. until false;
  1015. end;
  1016. function TFormOutput.HandleInputDiscard(ABuffer: pchar; ASize: integer): integer;
  1017. begin
  1018. Result := ASize;
  1019. end;
  1020. function TFormOutput.HandleInput(ABuffer: pchar; ASize: integer): integer;
  1021. begin
  1022. Result := FHandleInput(ABuffer, ASize);
  1023. end;
  1024. function TFormOutput.FillBuffer: TWriteBlockStatus;
  1025. begin
  1026. Result := wsDone;
  1027. if Assigned(FOnFillBuffer) then
  1028. FOnFillBuffer(Self, Result);
  1029. end;
  1030. procedure TFormOutput.DeleteCookie(const AName: string; const APath: string = '/';
  1031. const ADomain: string = '');
  1032. begin
  1033. { cookies expire when expires is in the past, duh }
  1034. SetCookie(AName, '', Now - 7.0, APath, ADomain);
  1035. end;
  1036. procedure TFormOutput.SetCookie(const AName, AValue: string; const AExpires: TDateTime;
  1037. const APath: string = '/'; const ADomain: string = '');
  1038. var
  1039. headers: PStringBuffer;
  1040. begin
  1041. headers := @TLHTTPServerSocket(FSocket).FHeaderOut.ExtraHeaders;
  1042. AppendString(headers^, 'Set-Cookie: ' + HTTPEncode(AName) + '=' + HTTPEncode(AValue));
  1043. AppendString(headers^, ';path=' + APath + ';expires=' + FormatDateTime(HTTPDateFormat, AExpires));
  1044. if Length(ADomain) > 0 then
  1045. begin
  1046. AppendString(headers^, ';domain=');
  1047. AppendString(headers^, ADomain);
  1048. end;
  1049. AppendString(headers^, #13#10);
  1050. end;
  1051. { TFormHandler }
  1052. procedure TFormHandler.SelectMultipart(AFormOutput: TFormOutput; AContentType: pchar);
  1053. var
  1054. boundary, endquote: pchar;
  1055. begin
  1056. boundary := StrScan(AContentType, '=');
  1057. if boundary <> nil then
  1058. begin
  1059. Inc(boundary);
  1060. if boundary^ = '"' then
  1061. begin
  1062. Inc(boundary);
  1063. endquote := StrScan(boundary, '"');
  1064. if endquote <> nil then
  1065. endquote^ := #0;
  1066. end;
  1067. end;
  1068. AFormOutput.FBoundary := boundary;
  1069. AFormOutput.FHandleInput := @AFormOutput.HandleInputMultipart;
  1070. end;
  1071. function TFormHandler.HandleURI(ASocket: TLHTTPServerSocket): TOutputItem;
  1072. var
  1073. newFormOutput: TFormOutput;
  1074. contentType: pchar;
  1075. begin
  1076. if not Assigned(FOnHandleURI) then
  1077. exit(nil);
  1078. newFormOutput := FOnHandleURI(ASocket);
  1079. if newFormOutput = nil then
  1080. exit(nil);
  1081. newFormOutput.AddVariables(ASocket.FRequestInfo.QueryParams, -1, URIParamSepChar);
  1082. newFormOutput.AddVariables(ASocket.Parameters[hpCookie], -1, CookieSepChar);
  1083. contentType := TLHTTPServerSocket(ASocket).Parameters[hpContentType];
  1084. if StrIComp(contentType, FormURLContentType) = 0 then
  1085. newFormOutput.FHandleInput := @newFormOutput.HandleInputFormURL
  1086. else if StrIComp(contentType, MultipartContentType) = 0 then
  1087. SelectMultipart(newFormOutput, contentType)
  1088. else
  1089. newFormOutput.FHandleInput := @newFormOutput.HandleInputDiscard;
  1090. Result := newFormOutput;
  1091. end;
  1092. end.