lwebserver.pp 34 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258
  1. { Web server component, built on the HTTP server component
  2. Copyright (C) 2006-2008 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 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_base;
  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;
  548. tempStr: string;
  549. begin
  550. lServerSocket := TLHTTPServerSocket(FSocket);
  551. {
  552. FProcess.Environment.Add('SERVER_ADDR=');
  553. FProcess.Environment.Add('SERVER_ADMIN=');
  554. FProcess.Environment.Add('SERVER_NAME=');
  555. FProcess.Environment.Add('SERVER_PORT=');
  556. }
  557. Self := nil;
  558. tempStr := TLHTTPServer(lServerSocket.Creator).ServerSoftware;
  559. if Length(tempStr) > 0 then
  560. AddEnvironment('SERVER_SOFTWARE', tempStr);
  561. AddEnvironment('GATEWAY_INTERFACE', 'CGI/1.1');
  562. AddEnvironment('SERVER_PROTOCOL', lServerSocket.FRequestInfo.VersionStr);
  563. AddEnvironment('REQUEST_METHOD', lServerSocket.FRequestInfo.Method);
  564. AddEnvironment('REQUEST_URI', '/'+lServerSocket.FRequestInfo.Argument);
  565. if Length(FExtraPath) > 0 then
  566. begin
  567. AddEnvironment('PATH_INFO', FExtraPath);
  568. { do not set PATH_TRANSLATED: bug in PHP }
  569. // AddEnvironment('PATH_TRANSLATED', DocumentRoot+FExtraPath);
  570. end;
  571. AddEnvironment('SCRIPT_NAME', FScriptName);
  572. AddEnvironment('SCRIPT_FILENAME', FScriptFileName);
  573. AddEnvironment('QUERY_STRING', lServerSocket.FRequestInfo.QueryParams);
  574. AddHTTPParam('CONTENT_TYPE', hpContentType);
  575. AddHTTPParam('CONTENT_LENGTH', hpContentLength);
  576. AddEnvironment('REMOTE_ADDR', FSocket.PeerAddress);
  577. AddEnvironment('REMOTE_PORT', IntToStr(FSocket.LocalPort));
  578. { used when user has authenticated in some way to server }
  579. // AddEnvironment('AUTH_TYPE='+...);
  580. // AddEnvironment('REMOTE_USER='+...);
  581. AddEnvironment('DOCUMENT_ROOT', FDocumentRoot);
  582. AddEnvironment('REDIRECT_STATUS', '200');
  583. AddHTTPParam('HTTP_HOST', hpHost);
  584. AddHTTPParam('HTTP_COOKIE', hpCookie);
  585. AddHTTPParam('HTTP_CONNECTION', hpConnection);
  586. AddHTTPParam('HTTP_REFERER', hpReferer);
  587. AddHTTPParam('HTTP_USER_AGENT', hpUserAgent);
  588. AddHTTPParam('HTTP_ACCEPT', hpAccept);
  589. AddEnvironment('PATH', FEnvPath);
  590. FParsingHeaders := true;
  591. FReadPos := FBufferPos;
  592. FParsePos := FBuffer+FReadPos;
  593. end;
  594. function TCGIOutput.ParseHeaders: boolean;
  595. var
  596. lHttpStatus: TLHTTPStatus;
  597. iEnd, lCode: integer;
  598. lStatus, lLength: dword;
  599. pLineEnd, pNextLine, pValue: pchar;
  600. lServerSocket: TLHTTPServerSocket;
  601. procedure AddExtraHeader;
  602. begin
  603. AppendString(lServerSocket.FHeaderOut.ExtraHeaders,
  604. FParsePos + ': ' + pValue + #13#10);
  605. end;
  606. begin
  607. lServerSocket := TLHTTPServerSocket(FSocket);
  608. repeat
  609. iEnd := IndexByte(FParsePos^, @FBuffer[FReadPos]-FParsePos, 10);
  610. if iEnd = -1 then exit(false);
  611. pNextLine := FParsePos+iEnd+1;
  612. if (iEnd > 0) and (FParsePos[iEnd-1] = #13) then
  613. dec(iEnd);
  614. pLineEnd := FParsePos+iEnd;
  615. pLineEnd^ := #0;
  616. if pLineEnd = FParsePos then
  617. begin
  618. { empty line signals end of headers }
  619. FParsingHeaders := false;
  620. FBufferOffset := pNextLine-FBuffer;
  621. FBufferPos := FReadPos;
  622. FReadPos := 0;
  623. lServerSocket.StartResponse(Self, true);
  624. exit(false);
  625. end;
  626. iEnd := IndexByte(FParsePos^, iEnd, ord(':'));
  627. if (iEnd = -1) or (FParsePos[iEnd+1] <> ' ') then
  628. break;
  629. FParsePos[iEnd] := #0;
  630. pValue := FParsePos+iEnd+2;
  631. if StrIComp(FParsePos, 'Content-type') = 0 then
  632. begin
  633. lServerSocket.FResponseInfo.ContentType := pValue;
  634. end else
  635. if StrIComp(FParsePos, 'Location') = 0 then
  636. begin
  637. if StrLIComp(pValue, 'http://', 7) = 0 then
  638. begin
  639. lServerSocket.FResponseInfo.Status := hsMovedPermanently;
  640. { add location header as-is to response }
  641. AddExtraHeader;
  642. end else
  643. InternalWrite('WARNING: unimplemented ''Location'' response received from CGI script');
  644. end else
  645. if StrIComp(FParsePos, 'Status') = 0 then
  646. begin
  647. { sometimes we get '<status code> space <reason>' }
  648. iEnd := IndexByte(pValue^, pLineEnd-pValue, ord(' '));
  649. if iEnd <> -1 then
  650. pValue[iEnd] := #0;
  651. Val(pValue, lStatus, lCode);
  652. if lCode <> 0 then
  653. break;
  654. for lHttpStatus := Low(TLHTTPStatus) to High(TLHTTPStatus) do
  655. if HTTPStatusCodes[lHttpStatus] = lStatus then
  656. lServerSocket.FResponseInfo.Status := lHttpStatus;
  657. end else
  658. if StrIComp(FParsePos, 'Content-Length') = 0 then
  659. begin
  660. Val(pValue, lLength, lCode);
  661. if lCode <> 0 then
  662. break;
  663. lServerSocket.FHeaderOut.ContentLength := lLength;
  664. end else
  665. if StrIComp(FParsePos, 'Last-Modified') = 0 then
  666. begin
  667. if not TryHTTPDateStrToDateTime(pValue,
  668. lServerSocket.FResponseInfo.LastModified) then
  669. InternalWrite('WARNING: unable to parse last-modified string from CGI script: ' + pValue);
  670. end else
  671. AddExtraHeader;
  672. FParsePos := pNextLine;
  673. until false;
  674. { error happened }
  675. lServerSocket.FResponseInfo.Status := hsInternalError;
  676. exit(true);
  677. end;
  678. function TCGIOutput.FillBuffer: TWriteBlockStatus;
  679. begin
  680. if not FParsingHeaders then
  681. FReadPos := FBufferPos;
  682. Result := WriteCGIData;
  683. if FParsingHeaders then
  684. begin
  685. if ParseHeaders then
  686. begin
  687. { error while parsing }
  688. FEof := true;
  689. exit(wsDone);
  690. end;
  691. end else
  692. FBufferPos := FReadPos;
  693. end;
  694. procedure TCGIOutput.WriteCGIBlock;
  695. begin
  696. { CGI process has output pending, we can write a block to socket }
  697. if FParsingHeaders then
  698. begin
  699. if (FillBuffer = wsDone) and FParsingHeaders then
  700. begin
  701. { still parsing headers ? something's wrong }
  702. FParsingHeaders := false;
  703. CGIOutputError;
  704. TLHTTPServerSocket(FSocket).StartResponse(Self);
  705. end;
  706. end;
  707. if not FParsingHeaders then
  708. FSocket.WriteBlock;
  709. end;
  710. { TSimpleCGIOutput }
  711. constructor TSimpleCGIOutput.Create(ASocket: TLHTTPSocket);
  712. begin
  713. inherited;
  714. FProcess := TLProcess.Create(nil);
  715. FProcess.Options := FProcess.Options + [poUsePipes];
  716. FProcess.OnNeedInput := @CGIProcNeedInput;
  717. FProcess.OnHasOutput := @CGIProcHasOutput;
  718. FProcess.OnHasStderr := @CGIProcHasStderr;
  719. end;
  720. destructor TSimpleCGIOutput.Destroy;
  721. begin
  722. inherited;
  723. FProcess.Free;
  724. end;
  725. function TSimpleCGIOutput.WriteCGIData: TWriteBlockStatus;
  726. var
  727. lRead: integer;
  728. begin
  729. lRead := FProcess.Output.Read(FBuffer[FReadPos], FBufferSize-FReadPos);
  730. if lRead = 0 then exit(wsDone);
  731. Inc(FReadPos, lRead);
  732. Result := InputBufferEmptyToWriteStatus[lRead = 0];
  733. end;
  734. procedure TSimpleCGIOutput.AddEnvironment(const AName, AValue: string);
  735. begin
  736. FProcess.Environment.Add(AName+'='+AValue);
  737. end;
  738. procedure TSimpleCGIOutput.DoneInput;
  739. begin
  740. FProcess.CloseInput;
  741. end;
  742. function TSimpleCGIOutput.HandleInput(ABuffer: pchar; ASize: integer): integer;
  743. begin
  744. if ASize > 0 then
  745. Result := FProcess.Input.Write(ABuffer^, ASize)
  746. else
  747. Result := 0;
  748. FProcess.InputEvent.IgnoreWrite := ASize = 0;
  749. end;
  750. procedure TSimpleCGIOutput.StartRequest;
  751. begin
  752. inherited;
  753. FProcess.Eventer := FSocket.Eventer;
  754. FProcess.Execute;
  755. end;
  756. procedure TSimpleCGIOutput.CGIOutputError;
  757. var
  758. ServerSocket: TLHTTPServerSocket;
  759. begin
  760. ServerSocket := TLHTTPServerSocket(FSocket);
  761. if FProcess.ExitStatus = 127 then
  762. ServerSocket.FResponseInfo.Status := hsNotFound
  763. else
  764. ServerSocket.FResponseInfo.Status := hsInternalError;
  765. end;
  766. procedure TSimpleCGIOutput.CGIProcNeedInput(AHandle: TLHandle);
  767. begin
  768. FProcess.InputEvent.IgnoreWrite := true;
  769. FSocket.ParseBuffer;
  770. end;
  771. procedure TSimpleCGIOutput.CGIProcHasOutput(AHandle: TLHandle);
  772. begin
  773. WriteCGIBlock;
  774. end;
  775. procedure TSimpleCGIOutput.CGIProcHasStderr(AHandle: TLHandle);
  776. var
  777. lBuf: array[0..1023] of char;
  778. lRead: integer;
  779. begin
  780. lRead := FProcess.Stderr.Read(lBuf, sizeof(lBuf)-1);
  781. lBuf[lRead] := #0;
  782. write(pchar(@lBuf[0]));
  783. end;
  784. { TFastCGIOutput }
  785. constructor TFastCGIOutput.Create(ASocket: TLHTTPSocket);
  786. begin
  787. inherited;
  788. end;
  789. destructor TFastCGIOutput.Destroy;
  790. begin
  791. if FRequest <> nil then
  792. begin
  793. FRequest.OnInput := nil;
  794. FRequest.OnOutput := nil;
  795. FRequest.OnStderr := nil;
  796. FRequest.OnEndRequest := nil;
  797. FRequest.AbortRequest;
  798. end;
  799. inherited;
  800. end;
  801. procedure TFastCGIOutput.AddEnvironment(const AName, AValue: string);
  802. begin
  803. FRequest.SendParam(AName, AValue);
  804. end;
  805. procedure TFastCGIOutput.CGIOutputError;
  806. begin
  807. TLHTTPServerSocket(FSocket).FResponseInfo.Status := hsInternalError;
  808. end;
  809. procedure TFastCGIOutput.DoneInput;
  810. begin
  811. if FRequest <> nil then
  812. FRequest.DoneInput;
  813. end;
  814. procedure TFastCGIOutput.RequestEnd(ARequest: TLFastCGIRequest);
  815. begin
  816. FRequest.OnEndRequest := nil;
  817. FRequest.OnInput := nil;
  818. FRequest.OnOutput := nil;
  819. FRequest := nil;
  820. { trigger final write, to flush output to socket }
  821. WriteCGIBlock;
  822. end;
  823. procedure TFastCGIOutput.RequestNeedInput(ARequest: TLFastCGIRequest);
  824. begin
  825. FSocket.ParseBuffer;
  826. end;
  827. procedure TFastCGIOutput.RequestHasOutput(ARequest: TLFastCGIRequest);
  828. begin
  829. WriteCGIBlock;
  830. end;
  831. procedure TFastCGIOutput.RequestHasStderr(ARequest: TLFastCGIRequest);
  832. var
  833. lBuf: array[0..1023] of char;
  834. lRead: integer;
  835. begin
  836. lRead := ARequest.Get(lBuf, sizeof(lBuf)-1);
  837. lBuf[lRead] := #0;
  838. write(pchar(@lBuf[0]));
  839. end;
  840. function TFastCGIOutput.HandleInput(ABuffer: pchar; ASize: integer): integer;
  841. begin
  842. Result := FRequest.SendInput(ABuffer, ASize);
  843. end;
  844. function TFastCGIOutput.WriteCGIData: TWriteBlockStatus;
  845. var
  846. lRead: integer;
  847. begin
  848. if FRequest = nil then exit(wsDone);
  849. if FRequest.OutputDone then exit(wsDone);
  850. lRead := FRequest.Get(@FBuffer[FReadPos], FBufferSize-FReadPos);
  851. Inc(FReadPos, lRead);
  852. Result := InputBufferEmptyToWriteStatus[lRead = 0];
  853. end;
  854. function TFastCGIOutput.WriteBlock: TWriteBlockStatus;
  855. begin
  856. if (FRequest <> nil) and FRequest.OutputPending then
  857. begin
  858. FRequest.ParseClientBuffer;
  859. Result := wsWaitingData;
  860. end else
  861. Result := inherited;
  862. end;
  863. procedure TFastCGIOutput.StartRequest;
  864. begin
  865. FRequest.OnEndRequest := @RequestEnd;
  866. FRequest.OnInput := @RequestNeedInput;
  867. FRequest.OnOutput := @RequestHasOutput;
  868. FRequest.OnStderr := @RequestHasStderr;
  869. inherited;
  870. FRequest.DoneParams;
  871. end;
  872. { TFormOutput }
  873. constructor TFormOutput.Create(ASocket: TLHTTPSocket);
  874. begin
  875. inherited;
  876. FRequestVars := TStringList.Create;
  877. end;
  878. destructor TFormOutput.Destroy;
  879. var
  880. I: integer;
  881. tmpObj: TObject;
  882. begin
  883. for I := 0 to FRequestVars.Count - 1 do
  884. begin
  885. tmpObj := FRequestVars.Objects[I];
  886. Finalize(string(tmpObj));
  887. FRequestVars.Objects[I] := nil;
  888. end;
  889. FRequestVars.Free;
  890. inherited;
  891. end;
  892. function TFormOutput.AddVariables(Variables: pchar; ASize: integer; SepChar: char): integer;
  893. var
  894. varname, sep, next: pchar;
  895. strName, strValue: string;
  896. tmpObj: TObject;
  897. i: integer;
  898. begin
  899. if Variables = nil then
  900. exit(0);
  901. if ASize = -1 then
  902. ASize := StrLen(Variables);
  903. varname := Variables;
  904. repeat
  905. sep := varname + IndexChar(varname^, ASize, '=');
  906. if sep < varname then
  907. break;
  908. dec(ASize, sep-varname);
  909. next := sep + IndexChar(sep^, ASize, SepChar);
  910. if next < sep then
  911. begin
  912. next := sep + ASize;
  913. ASize := 0;
  914. end else
  915. dec(ASize, next+1-sep);
  916. if sep > varname then
  917. begin
  918. setlength(strName, sep-varname);
  919. move(varname[0], strName[1], sep-varname);
  920. setlength(strValue, next-sep-1);
  921. move(sep[1], strValue[1], next-sep-1);
  922. i := FRequestVars.Add(strName);
  923. tmpObj := nil;
  924. string(tmpObj) := strValue;
  925. FRequestVars.Objects[i] := tmpObj;
  926. end;
  927. varname := next+1;
  928. until false;
  929. Result := ASize;
  930. end;
  931. procedure TFormOutput.DoneInput;
  932. begin
  933. if Assigned(FOnExtraHeaders) then
  934. FOnExtraHeaders(Self);
  935. TLHTTPServerSocket(FSocket).StartResponse(Self);
  936. end;
  937. function TFormOutput.HandleInputFormURL(ABuffer: pchar; ASize: integer): integer;
  938. begin
  939. Result := ASize-AddVariables(ABuffer, ASize, URIParamSepChar)
  940. end;
  941. procedure TFormOutput.ParseMultipartHeader(ABuffer, ALineEnd: pchar);
  942. var
  943. I: TLMultipartParameter;
  944. len: integer;
  945. begin
  946. for I := Low(TLMultipartParameter) to High(TLMultipartParameter) do
  947. begin
  948. len := Length(MPParameterStrings[I]);
  949. if ABuffer+len >= ALineEnd then
  950. continue;
  951. if (ABuffer[len] = ':')
  952. and (StrLIComp(ABuffer, PChar(MPParameterStrings[I]), len) = 0) then
  953. begin
  954. Inc(ABuffer, len+2);
  955. repeat
  956. if ABuffer = ALineEnd then exit;
  957. if ABuffer^ <> ' ' then break;
  958. inc(ABuffer);
  959. until false;
  960. FMPParameters[I] := ABuffer;
  961. if I = mpContentType then
  962. begin
  963. repeat
  964. if ABuffer = ALineEnd then exit;
  965. if ABuffer = ';' then break;
  966. inc(ABuffer);
  967. until false;
  968. end;
  969. break;
  970. end;
  971. end;
  972. end;
  973. function TFormOutput.FindBoundary(ABuffer: pchar): pchar;
  974. begin
  975. {$warning TODO}
  976. Result := nil;
  977. end;
  978. function TFormOutput.HandleInputMultipart(ABuffer: pchar; ASize: integer): integer;
  979. var
  980. pos, next, endline: pchar;
  981. begin
  982. pos := ABuffer;
  983. repeat
  984. case FMPState of
  985. msStart:
  986. begin
  987. { discard until first boundary }
  988. next := FindBoundary(pos);
  989. if next = nil then
  990. exit(ASize);
  991. FMPState := msBodypartHeader;
  992. end;
  993. msBodypartHeader:
  994. begin
  995. endline := pos + IndexChar(pos, ASize, #10);
  996. if endline < pos then
  997. exit(pos-ABuffer);
  998. next := endline+1;
  999. if (endline > pos) and ((endline-1)^ = #13) then
  1000. dec(endline);
  1001. endline^ := #0;
  1002. if endline > pos then
  1003. ParseMultipartHeader(pos, endline)
  1004. else
  1005. FMPState := msBodypartData;
  1006. end;
  1007. msBodypartData:
  1008. begin
  1009. { decode based on content-transfer-encoding ? }
  1010. { CRLF before boundary, belongs to boundary, not data! }
  1011. next := FindBoundary(ABuffer);
  1012. end;
  1013. else
  1014. exit(ASize);
  1015. end;
  1016. dec(ASize, next-pos);
  1017. pos := next;
  1018. until false;
  1019. end;
  1020. function TFormOutput.HandleInputDiscard(ABuffer: pchar; ASize: integer): integer;
  1021. begin
  1022. Result := ASize;
  1023. end;
  1024. function TFormOutput.HandleInput(ABuffer: pchar; ASize: integer): integer;
  1025. begin
  1026. Result := FHandleInput(ABuffer, ASize);
  1027. end;
  1028. function TFormOutput.FillBuffer: TWriteBlockStatus;
  1029. begin
  1030. Result := wsDone;
  1031. if Assigned(FOnFillBuffer) then
  1032. FOnFillBuffer(Self, Result);
  1033. end;
  1034. procedure TFormOutput.DeleteCookie(const AName: string; const APath: string = '/';
  1035. const ADomain: string = '');
  1036. begin
  1037. { cookies expire when expires is in the past, duh }
  1038. SetCookie(AName, '', Now - 7.0, APath, ADomain);
  1039. end;
  1040. procedure TFormOutput.SetCookie(const AName, AValue: string; const AExpires: TDateTime;
  1041. const APath: string = '/'; const ADomain: string = '');
  1042. var
  1043. headers: PStringBuffer;
  1044. begin
  1045. headers := @TLHTTPServerSocket(FSocket).FHeaderOut.ExtraHeaders;
  1046. AppendString(headers^, 'Set-Cookie: ' + HTTPEncode(AName) + '=' + HTTPEncode(AValue));
  1047. AppendString(headers^, ';path=' + APath + ';expires=' + FormatDateTime(HTTPDateFormat, AExpires));
  1048. if Length(ADomain) > 0 then
  1049. begin
  1050. AppendString(headers^, ';domain=');
  1051. AppendString(headers^, ADomain);
  1052. end;
  1053. AppendString(headers^, #13#10);
  1054. end;
  1055. { TFormHandler }
  1056. procedure TFormHandler.SelectMultipart(AFormOutput: TFormOutput; AContentType: pchar);
  1057. var
  1058. boundary, endquote: pchar;
  1059. begin
  1060. boundary := StrScan(AContentType, '=');
  1061. if boundary <> nil then
  1062. begin
  1063. Inc(boundary);
  1064. if boundary^ = '"' then
  1065. begin
  1066. Inc(boundary);
  1067. endquote := StrScan(boundary, '"');
  1068. if endquote <> nil then
  1069. endquote^ := #0;
  1070. end;
  1071. end;
  1072. AFormOutput.FBoundary := boundary;
  1073. AFormOutput.FHandleInput := @AFormOutput.HandleInputMultipart;
  1074. end;
  1075. function TFormHandler.HandleURI(ASocket: TLHTTPServerSocket): TOutputItem;
  1076. var
  1077. newFormOutput: TFormOutput;
  1078. contentType: pchar;
  1079. begin
  1080. if not Assigned(FOnHandleURI) then
  1081. exit(nil);
  1082. newFormOutput := FOnHandleURI(ASocket);
  1083. if newFormOutput = nil then
  1084. exit(nil);
  1085. newFormOutput.AddVariables(ASocket.FRequestInfo.QueryParams, -1, URIParamSepChar);
  1086. newFormOutput.AddVariables(ASocket.Parameters[hpCookie], -1, CookieSepChar);
  1087. contentType := TLHTTPServerSocket(ASocket).Parameters[hpContentType];
  1088. if StrIComp(contentType, FormURLContentType) = 0 then
  1089. newFormOutput.FHandleInput := @newFormOutput.HandleInputFormURL
  1090. else if StrIComp(contentType, MultipartContentType) = 0 then
  1091. SelectMultipart(newFormOutput, contentType)
  1092. else
  1093. newFormOutput.FHandleInput := @newFormOutput.HandleInputDiscard;
  1094. Result := newFormOutput;
  1095. end;
  1096. end.