IdHTTPWebBrokerBridge.pas 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002
  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.7 6/26/2004 12:11:16 AM BGooijen
  18. updates for D8
  19. Rev 1.6 4/8/2004 4:00:40 PM BGooijen
  20. Fix for D8
  21. Rev 1.5 07/04/2004 20:44:06 HHariri
  22. Updates
  23. Rev 1.4 07/04/2004 20:07:50 HHariri
  24. Updates for .NET
  25. Rev 1.3 10/19/2003 4:50:10 PM DSiders
  26. Added localization comments.
  27. Rev 1.2 10/12/2003 1:49:48 PM BGooijen
  28. Changed comment of last checkin
  29. Rev 1.1 10/12/2003 1:43:32 PM BGooijen
  30. Changed IdCompilerDefines.inc to Core\IdCompilerDefines.inc
  31. Rev 1.0 11/13/2002 07:54:34 AM JPMugaas
  32. }
  33. unit IdHTTPWebBrokerBridge;
  34. {
  35. Original Author: Dave Nottage.
  36. Modified by: Grahame Grieve
  37. Modified by: Chad Z. Hower (Kudzu)
  38. }
  39. interface
  40. {$i IdCompilerDefines.inc}
  41. uses
  42. Classes,
  43. HTTPApp,
  44. SysUtils,
  45. IdContext, IdCustomHTTPServer, IdException, IdTCPServer, IdIOHandlerSocket,
  46. WebReq;
  47. type
  48. EWBBException = class(EIdException);
  49. EWBBInvalidIdxGetDateVariable = class(EWBBException);
  50. EWBBInvalidIdxSetDateVariable = class(EWBBException );
  51. EWBBInvalidIdxGetIntVariable = class(EWBBException );
  52. EWBBInvalidIdxSetIntVariable = class(EWBBException );
  53. EWBBInvalidIdxGetStrVariable = class(EWBBException);
  54. EWBBInvalidIdxSetStringVar = class(EWBBException);
  55. EWBBInvalidStringVar = class(EWBBException);
  56. {$IFNDEF DCC_10_1_OR_ABOVE}
  57. {$DEFINE WBB_ANSI}
  58. {$ELSE}
  59. {$UNDEF WBB_ANSI}
  60. {$ENDIF}
  61. {$IFDEF DCC_11_OR_ABOVE}
  62. {$DEFINE WBB_BIG_INTS}
  63. {$ELSE}
  64. {$UNDEF WBB_BIG_INTS}
  65. {$ENDIF}
  66. TIdHTTPAppRequest = class(TWebRequest)
  67. protected
  68. FRequestInfo : TIdHTTPRequestInfo;
  69. FResponseInfo : TIdHTTPResponseInfo;
  70. FThread : TIdContext;
  71. FContentStream : TStream;
  72. FFreeContentStream : Boolean;
  73. //
  74. function GetDateVariable(Index: Integer): TDateTime; override;
  75. function GetIntegerVariable(Index: Integer): {$IFDEF WBB_BIG_INTS}Int64{$ELSE}Integer{$ENDIF}; override;
  76. function GetStringVariable(Index: Integer): {$IFDEF WBB_ANSI}AnsiString{$ELSE}string{$ENDIF}; override;
  77. {$IFDEF DCC_XE_OR_ABOVE}
  78. function GetRemoteIP: string; override;
  79. function GetRawPathInfo: {$IFDEF WBB_ANSI}AnsiString{$ELSE}string{$ENDIF}; override;
  80. {$ENDIF}
  81. {$IFDEF DCC_10_1_OR_ABOVE}
  82. function GetRawContent: TBytes; override;
  83. {$ENDIF}
  84. public
  85. constructor Create(AThread: TIdContext; ARequestInfo: TIdHTTPRequestInfo;
  86. AResponseInfo: TIdHTTPResponseInfo);
  87. destructor Destroy; override;
  88. {$IFDEF WBB_ANSI}
  89. function GetFieldByName(const Name: AnsiString): AnsiString; override;
  90. {$ELSE}
  91. function GetFieldByName(const Name: string): string; override;
  92. {$ENDIF}
  93. function ReadClient(var Buffer; Count: Integer): Integer; override;
  94. function ReadString(Count: Integer): {$IFDEF WBB_ANSI}AnsiString{$ELSE}string{$ENDIF}; override;
  95. {function ReadUnicodeString(Count: Integer): string;}
  96. function TranslateURI(const URI: string): string; override;
  97. function WriteClient(var ABuffer; ACount: Integer): Integer; override;
  98. function WriteHeaders(StatusCode: Integer; const ReasonString, Headers: {$IFDEF WBB_ANSI}AnsiString{$ELSE}string{$ENDIF}): Boolean; override;
  99. function WriteString(const AString: {$IFDEF WBB_ANSI}AnsiString{$ELSE}string{$ENDIF}): Boolean; override;
  100. end;
  101. TIdHTTPAppResponse = class(TWebResponse)
  102. protected
  103. FContent: string;
  104. FRequestInfo: TIdHTTPRequestInfo;
  105. FResponseInfo: TIdHTTPResponseInfo;
  106. FSent: Boolean;
  107. FThread: TIdContext;
  108. FContentType: {$IFDEF WBB_ANSI}AnsiString{$ELSE}string{$ENDIF}; // Workaround to preserve value of ContentType property
  109. //
  110. function GetContent: {$IFDEF WBB_ANSI}AnsiString{$ELSE}string{$ENDIF}; override;
  111. function GetDateVariable(Index: Integer): TDateTime; override;
  112. function GetStatusCode: Integer; override;
  113. function GetIntegerVariable(Index: Integer): {$IFDEF WBB_BIG_INTS}Int64{$ELSE}Integer{$ENDIF}; override;
  114. function GetLogMessage: string; override;
  115. function GetStringVariable(Index: Integer): {$IFDEF WBB_ANSI}AnsiString{$ELSE}string{$ENDIF}; override;
  116. procedure SetContent(const AValue: {$IFDEF WBB_ANSI}AnsiString{$ELSE}string{$ENDIF}); override;
  117. procedure SetContentStream(AValue: TStream); override;
  118. procedure SetStatusCode(AValue: Integer); override;
  119. procedure SetStringVariable(Index: Integer; const Value: {$IFDEF WBB_ANSI}AnsiString{$ELSE}string{$ENDIF}); override;
  120. procedure SetDateVariable(Index: Integer; const Value: TDateTime); override;
  121. procedure SetIntegerVariable(Index: Integer; Value: {$IFDEF WBB_BIG_INTS}Int64{$ELSE}Integer{$ENDIF}); override;
  122. procedure SetLogMessage(const Value: string); override;
  123. procedure MoveCookiesAndCustomHeaders;
  124. public
  125. constructor Create(AHTTPRequest: TWebRequest; AThread: TIdContext;
  126. ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
  127. procedure SendRedirect(const URI: {$IFDEF WBB_ANSI}AnsiString{$ELSE}string{$ENDIF}); override;
  128. procedure SendResponse; override;
  129. procedure SendStream(AStream: TStream); override;
  130. function Sent: Boolean; override;
  131. end;
  132. TIdHTTPWebBrokerBridge = class(TIdCustomHTTPServer)
  133. private
  134. procedure RunWebModuleClass(AThread: TIdContext; ARequestInfo: TIdHTTPRequestInfo;
  135. AResponseInfo: TIdHTTPResponseInfo);
  136. protected
  137. FWebModuleClass: TComponentClass;
  138. //
  139. procedure DoCommandGet(AThread: TIdContext; ARequestInfo: TIdHTTPRequestInfo;
  140. AResponseInfo: TIdHTTPResponseInfo); override;
  141. procedure DoCommandOther(AThread: TIdContext; ARequestInfo: TIdHTTPRequestInfo;
  142. AResponseInfo: TIdHTTPResponseInfo); override;
  143. public
  144. constructor Create(AOwner: TComponent); override;
  145. procedure RegisterWebModuleClass(AClass: TComponentClass);
  146. end;
  147. implementation
  148. uses
  149. IdResourceStringsProtocols,
  150. IdBuffer, IdHTTPHeaderInfo, IdGlobal, IdGlobalProtocols, IdCookie, IdCharsets,
  151. Math
  152. {$IFDEF HAS_TNetEncoding}
  153. , System.NetEncoding
  154. {$ENDIF}
  155. ;
  156. type
  157. // Make HandleRequest accessible
  158. TWebDispatcherAccess = class(TCustomWebDispatcher);
  159. const
  160. INDEX_RESP_Version = 0;
  161. INDEX_RESP_ReasonString = 1;
  162. INDEX_RESP_Server = 2;
  163. INDEX_RESP_WWWAuthenticate = 3;
  164. INDEX_RESP_Realm = 4;
  165. INDEX_RESP_Allow = 5;
  166. INDEX_RESP_Location = 6;
  167. INDEX_RESP_ContentEncoding = 7;
  168. INDEX_RESP_ContentType = 8;
  169. INDEX_RESP_ContentVersion = 9;
  170. INDEX_RESP_DerivedFrom = 10;
  171. INDEX_RESP_Title = 11;
  172. //
  173. INDEX_RESP_ContentLength = 0;
  174. //
  175. INDEX_RESP_Date = 0;
  176. INDEX_RESP_Expires = 1;
  177. INDEX_RESP_LastModified = 2;
  178. //
  179. //Borland coder didn't define constants in HTTPApp
  180. INDEX_Method = 0;
  181. INDEX_ProtocolVersion = 1;
  182. INDEX_URL = 2;
  183. INDEX_Query = 3;
  184. INDEX_PathInfo = 4;
  185. INDEX_PathTranslated = 5;
  186. INDEX_CacheControl = 6;
  187. INDEX_Date = 7;
  188. INDEX_Accept = 8;
  189. INDEX_From = 9;
  190. INDEX_Host = 10;
  191. INDEX_IfModifiedSince = 11;
  192. INDEX_Referer = 12;
  193. INDEX_UserAgent = 13;
  194. INDEX_ContentEncoding = 14;
  195. INDEX_ContentType = 15;
  196. INDEX_ContentLength = 16;
  197. INDEX_ContentVersion = 17;
  198. INDEX_DerivedFrom = 18;
  199. INDEX_Expires = 19;
  200. INDEX_Title = 20;
  201. INDEX_RemoteAddr = 21;
  202. INDEX_RemoteHost = 22;
  203. INDEX_ScriptName = 23;
  204. INDEX_ServerPort = 24;
  205. INDEX_Content = 25;
  206. INDEX_Connection = 26;
  207. INDEX_Cookie = 27;
  208. INDEX_Authorization = 28;
  209. { TIdHTTPAppRequest }
  210. constructor TIdHTTPAppRequest.Create(AThread: TIdContext; ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
  211. var
  212. i: Integer;
  213. begin
  214. FThread := AThread;
  215. FRequestInfo := ARequestInfo;
  216. FResponseInfo := AResponseInfo;
  217. inherited Create;
  218. for i := 0 to ARequestInfo.Cookies.Count - 1 do begin
  219. CookieFields.Add(ARequestInfo.Cookies[i].ClientCookie);
  220. end;
  221. if Assigned(FRequestInfo.PostStream) then
  222. begin
  223. FContentStream := FRequestInfo.PostStream;
  224. FFreeContentStream := False;
  225. end else
  226. begin
  227. if FRequestInfo.FormParams <> '' then begin {do not localize}
  228. // an input form that was submitted as "application/www-url-encoded"...
  229. FContentStream := TStringStream.Create(FRequestInfo.FormParams);
  230. end else
  231. begin
  232. // anything else for now...
  233. FContentStream := TStringStream.Create(FRequestInfo.UnparsedParams);
  234. end;
  235. FFreeContentStream := True;
  236. end;
  237. end;
  238. destructor TIdHTTPAppRequest.Destroy;
  239. begin
  240. if FFreeContentStream then begin
  241. IdDisposeAndNil(FContentStream);
  242. end;
  243. inherited;
  244. end;
  245. function TIdHTTPAppRequest.GetDateVariable(Index: Integer): TDateTime;
  246. var
  247. LValue: string;
  248. begin
  249. LValue := string(GetStringVariable(Index));
  250. if LValue <> '' then begin
  251. Result := ParseDate(LValue);
  252. end else begin
  253. Result := -1;
  254. end;
  255. end;
  256. function TIdHTTPAppRequest.GetIntegerVariable(Index: Integer): {$IFDEF WBB_BIG_INTS}Int64{$ELSE}Integer{$ENDIF};
  257. begin
  258. Result := {$IFDEF WBB_BIG_INTS}StrToInt64Def{$ELSE}StrToIntDef{$ENDIF}(string(GetStringVariable(Index)), -1)
  259. end;
  260. {$IFDEF DCC_XE_OR_ABOVE}
  261. function TIdHTTPAppRequest.GetRawPathInfo: {$IFDEF WBB_ANSI}AnsiString{$ELSE}string{$ENDIF};
  262. begin
  263. {$IFDEF WBB_ANSI}
  264. Result := AnsiString(FRequestInfo.URI);
  265. {$ELSE}
  266. Result := FRequestInfo.URI;
  267. {$ENDIF}
  268. end;
  269. function TIdHTTPAppRequest.GetRemoteIP: string;
  270. begin
  271. Result := FRequestInfo.RemoteIP;
  272. end;
  273. {$ENDIF}
  274. {$IFDEF DCC_10_1_OR_ABOVE}
  275. function TIdHTTPAppRequest.GetRawContent: TBytes;
  276. var
  277. LPos: Int64;
  278. begin
  279. LPos := FContentStream.Position;
  280. FContentStream.Position := 0;
  281. try
  282. SetLength(Result, FContentStream.Size);
  283. FContentStream.ReadBuffer(PByte(Result)^, Length(Result));
  284. finally
  285. FContentStream.Position := LPos;
  286. end;
  287. end;
  288. {$ENDIF}
  289. function TIdHTTPAppRequest.GetStringVariable(Index: Integer): {$IFDEF WBB_ANSI}AnsiString{$ELSE}string{$ENDIF};
  290. var
  291. LValue: string;
  292. LPos: Int64;
  293. {$IFDEF WBB_ANSI}
  294. LCount: Integer;
  295. {$ENDIF}
  296. begin
  297. // RLebeau 1/15/2016: Now accessing FRequestInfo.RawHeaders.Values[] directly
  298. // instead of using GetFieldByName(). On Delphi versions prior to 10.1 Berlin,
  299. // GetFieldByName() returned an AnsiString, even in Unicode versions. So as not
  300. // to have to IFDEF all of these fields, now doing one conversion at the end of
  301. // this method, which means having a local String variable. Don't want the
  302. // overhead of performing an AnsiString->String->AnsiString conversion...
  303. case Index of
  304. INDEX_Method : LValue := FRequestInfo.Command;
  305. INDEX_ProtocolVersion : LValue := FRequestInfo.Version;
  306. //INDEX_URL : LValue := FRequestInfo.Document;
  307. INDEX_URL : LValue := ''; // Root - consistent with ISAPI which return path to root
  308. INDEX_Query : LValue := FRequestInfo.QueryParams;
  309. INDEX_PathInfo : LValue := FRequestInfo.Document;
  310. INDEX_PathTranslated : LValue := FRequestInfo.Document; // it's not clear quite what should be done here - we can't translate to a path
  311. INDEX_CacheControl : LValue := FRequestInfo.RawHeaders.Values['Cache-Control']; {do not localize}
  312. INDEX_Date : LValue := FRequestInfo.RawHeaders.Values['Date']; {do not localize}
  313. INDEX_Accept : LValue := FRequestInfo.Accept;
  314. INDEX_From : LValue := FRequestInfo.From;
  315. INDEX_Host: begin
  316. LValue := FRequestInfo.Host;
  317. LValue := Fetch(LValue, ':', False);
  318. end;
  319. INDEX_IfModifiedSince : LValue := FRequestInfo.RawHeaders.Values['If-Modified-Since']; {do not localize}
  320. INDEX_Referer : LValue := FRequestInfo.Referer;
  321. INDEX_UserAgent : LValue := FRequestInfo.UserAgent;
  322. INDEX_ContentEncoding : LValue := FRequestInfo.ContentEncoding;
  323. INDEX_ContentType : LValue := FRequestInfo.ContentType;
  324. INDEX_ContentLength : LValue := IntToStr(FContentStream.Size);
  325. INDEX_ContentVersion : LValue := FRequestInfo.RawHeaders.Values['CONTENT_VERSION']; {do not localize}
  326. INDEX_DerivedFrom : LValue := FRequestInfo.RawHeaders.Values['Derived-From']; {do not localize}
  327. INDEX_Expires : LValue := FRequestInfo.RawHeaders.Values['Expires']; {do not localize}
  328. INDEX_Title : LValue := FRequestInfo.RawHeaders.Values['Title']; {do not localize}
  329. INDEX_RemoteAddr : LValue := FRequestInfo.RemoteIP;
  330. INDEX_RemoteHost : LValue := FRequestInfo.RawHeaders.Values['REMOTE_HOST']; {do not localize}
  331. INDEX_ScriptName : LValue := '';
  332. INDEX_ServerPort: begin
  333. LValue := FRequestInfo.Host;
  334. Fetch(LValue, ':');
  335. if LValue = '' then begin
  336. LValue := IntToStr(FThread.Connection.Socket.Binding.Port);
  337. // LValue := '80';
  338. end;
  339. end;
  340. INDEX_Content: begin
  341. if FFreeContentStream then
  342. begin
  343. LValue := TStringStream(FContentStream).DataString;
  344. end else
  345. begin
  346. LPos := FContentStream.Position;
  347. FContentStream.Position := 0;
  348. try
  349. // TODO: just use TIdHTTPAppRequest.ReadString() instead?
  350. //s := ReadString(FContentStream.Size);
  351. {$IFDEF WBB_ANSI}
  352. // RLebeau 2/21/2009: not using ReadStringAsCharSet() anymore. Since
  353. // this method returns an AnsiString, the stream data should not be
  354. // decoded to Unicode and then converted to Ansi. That can lose
  355. // characters...
  356. // Result := ReadStringAsCharSet(FContentStream, FRequestInfo.CharSet);
  357. LCount := Integer(IndyMin(FContentStream.Size, MaxInt));
  358. if LCount > 0 then begin
  359. // instead of using a temp memory buffer, just pre-allocate the
  360. // Result to the size of the stream and then read directly into it...
  361. SetLength(Result, LCount);
  362. FContentStream.ReadBuffer(PAnsiChar(Result)^, LCount);
  363. // RLebeau 2/21/2009: For D2009+, the AnsiString payload should have
  364. // the proper codepage assigned to it as well so it can be converted
  365. // correctly if assigned to other string variables later on...
  366. SetCodePage(PRawByteString(@Result)^, CharsetToCodePage(FRequestInfo.CharSet), False);
  367. end else begin
  368. Result := '';
  369. end;
  370. {$ELSE}
  371. // RLebeau 1/15/2016: this method now returns a UnicodeString, so
  372. // lets use ReadStringAsCharSet() once again...
  373. Result := ReadStringAsCharset(FContentStream, FRequestInfo.CharSet);
  374. {$ENDIF}
  375. finally
  376. FContentStream.Position := LPos;
  377. end;
  378. Exit;
  379. end;
  380. end;
  381. INDEX_Connection : LValue := FRequestInfo.RawHeaders.Values['Connection']; {do not localize}
  382. INDEX_Cookie : LValue := ''; // not available at present. FRequestInfo.Cookies....;
  383. INDEX_Authorization : LValue := FRequestInfo.RawHeaders.Values['Authorization']; {do not localize}
  384. else
  385. LValue := '';
  386. end;
  387. Result := {$IFDEF WBB_ANSI}AnsiString(LValue){$ELSE}LValue{$ENDIF};
  388. end;
  389. {$IFDEF WBB_ANSI}
  390. function TIdHTTPAppRequest.GetFieldByName(const Name: AnsiString): AnsiString;
  391. begin
  392. Result := AnsiString(FRequestInfo.RawHeaders.Values[string(Name)]);
  393. end;
  394. {$ELSE}
  395. function TIdHTTPAppRequest.GetFieldByName(const Name: string): string;
  396. begin
  397. Result := FRequestInfo.RawHeaders.Values[Name];
  398. end;
  399. {$ENDIF}
  400. function TIdHTTPAppRequest.ReadClient(var Buffer; Count: Integer): Integer;
  401. begin
  402. Result := FContentStream.Read(Buffer, Count);
  403. // well, it shouldn't be less than 0. but let's not take chances
  404. if Result < 0 then begin
  405. Result := 0;
  406. end;
  407. end;
  408. function TIdHTTPAppRequest.ReadString(Count: Integer): {$IFDEF WBB_ANSI}AnsiString{$ELSE}string{$ENDIF};
  409. {$IFDEF WBB_ANSI}
  410. var
  411. LCount: Integer;
  412. {$ENDIF}
  413. begin
  414. {$IFDEF WBB_ANSI}
  415. // RLebeau 2/21/2009: not using ReadStringFromStream() anymore. Since
  416. // this method returns an AnsiString, the stream data should not be
  417. // decoded to Unicode and then converted to Ansi. That can lose
  418. // characters.
  419. // Result := AnsiString(ReadStringFromStream(FContentStream, Count));
  420. LCount := Integer(IndyLength(FContentStream, Count));
  421. if LCount > 0 then begin
  422. SetLength(Result, LCount);
  423. FContentStream.ReadBuffer(PAnsiChar(Result)^, LCount);
  424. // RLebeau 2/21/2009: For D2009+, the AnsiString payload should have
  425. // the proper codepage assigned to it as well so it can be converted
  426. // correctly if assigned to other string variables later on...
  427. SetCodePage(PRawByteString(@Result)^, CharsetToCodePage(FRequestInfo.CharSet), False);
  428. end else begin
  429. Result := '';
  430. end;
  431. {$ELSE}
  432. // RLebeau 1/15/2016: this method now returns a UnicodeString, so
  433. // lets use ReadStringFromStream() once again...
  434. Result := ReadStringFromStream(FContentStream, Count, CharsetToEncoding(FRequestInfo.CharSet));
  435. {$ENDIF}
  436. end;
  437. function TIdHTTPAppRequest.TranslateURI(const URI: string): string;
  438. begin
  439. // we don't have the concept of a path translation. It's not quite clear
  440. // what to do about this. Comments welcome ([email protected])
  441. Result := URI;
  442. end;
  443. function TIdHTTPAppRequest.WriteHeaders(StatusCode: Integer; const ReasonString, Headers: {$IFDEF WBB_ANSI}AnsiString{$ELSE}string{$ENDIF}): Boolean;
  444. begin
  445. FResponseInfo.ResponseNo := StatusCode;
  446. FResponseInfo.ResponseText := {$IFDEF WBB_ANSI}string(ReasonString){$ELSE}ReasonString{$ENDIF};
  447. FResponseInfo.CustomHeaders.Add({$IFDEF WBB_ANSI}string(Headers){$ELSE}Headers{$ENDIF});
  448. FResponseInfo.WriteHeader;
  449. Result := True;
  450. end;
  451. function TIdHTTPAppRequest.WriteString(const AString: {$IFDEF WBB_ANSI}AnsiString{$ELSE}string{$ENDIF}): Boolean;
  452. begin
  453. FThread.Connection.IOHandler.Write({$IFDEF WBB_ANSI}string(AString){$ELSE}AString{$ENDIF});
  454. Result := True;
  455. end;
  456. function TIdHTTPAppRequest.WriteClient(var ABuffer; ACount: Integer): Integer;
  457. var
  458. LStream: TStream;
  459. begin
  460. LStream := TIdReadOnlyMemoryBufferStream.Create(@ABuffer, ACount);
  461. try
  462. FThread.Connection.IOHandler.Write(LStream, 0, False);
  463. finally
  464. LStream.Free;
  465. end;
  466. Result := ACount;
  467. end;
  468. { TIdHTTPAppResponse }
  469. constructor TIdHTTPAppResponse.Create(AHTTPRequest: TWebRequest; AThread: TIdContext; ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
  470. begin
  471. FThread := AThread;
  472. FRequestInfo := ARequestInfo;
  473. FResponseInfo := AResponseInfo;
  474. inherited Create(AHTTPRequest);
  475. if FHTTPRequest.ProtocolVersion = '' then begin
  476. Version := '1.0'; {do not localize}
  477. end;
  478. StatusCode := 200;
  479. LastModified := -1;
  480. Expires := -1;
  481. Date := -1;
  482. // RLebeau 8/13/2015: no longer setting a default ContentType here. Doing so
  483. // sets a default CharSet, which would get carried over if the user assigns a
  484. // new *non-text* ContentType without an explicit charset. TAppResponse does
  485. // not expose access to the FResponseInfo.CharSet property. For example, if
  486. // the user sets TAppResponse.ContentType to 'image/jpeg', the resulting
  487. // Content-Type header woud be 'image/jpeg; charset=ISO-8859-1', which can
  488. // cause problems for some clients. Besides, TIdHTTPResponseInfo.WriteHeader()
  489. // sets the ContentType to 'text/html; charset=ISO-8859-1' if no ContentType
  490. // has been provided but there is ContentText/ContentStream data, so this is
  491. // redundant here anyway...
  492. //
  493. // ContentType := 'text/html'; {do not localize}
  494. end;
  495. function TIdHTTPAppResponse.GetContent: {$IFDEF WBB_ANSI}AnsiString{$ELSE}string{$ENDIF};
  496. {$IFDEF WBB_ANSI}
  497. var
  498. LBytes: TIdBytes;
  499. {$ENDIF}
  500. begin
  501. {$IFDEF WBB_ANSI}
  502. // RLebeau 2/21/2009: encode the content using the specified charset.
  503. Result := '';
  504. LBytes := CharsetToEncoding(FResponseInfo.CharSet).GetBytes(FResponseInfo.ContentText);
  505. SetString(Result, PAnsiChar(LBytes), Length(LBytes));
  506. // RLebeau 2/21/2009: for D2009+, the AnsiString payload should have
  507. // the proper codepage assigned to it as well so it can be converted
  508. // correctly if assigned to other string variables later on...
  509. SetCodePage(PRawByteString(@Result)^, CharsetToCodePage(FResponseInfo.CharSet), False);
  510. {$ELSE}
  511. Result := FResponseInfo.ContentText;
  512. {$ENDIF}
  513. end;
  514. function TIdHTTPAppResponse.GetLogMessage: string;
  515. begin
  516. Result := '';
  517. end;
  518. function TIdHTTPAppResponse.GetStatusCode: Integer;
  519. begin
  520. Result := FResponseInfo.ResponseNo;
  521. end;
  522. function TIdHTTPAppResponse.GetDateVariable(Index: Integer): TDateTime;
  523. // WebBroker apps are responsible for conversion to GMT, Indy HTTP server expects apps to pas local time
  524. function ToGMT(ADateTime: TDateTime): TDateTime;
  525. begin
  526. Result := ADateTime;
  527. if Result <> -1 then begin
  528. Result := LocalTimeToUTCTime(Result);
  529. end;
  530. end;
  531. begin
  532. //TODO: resource string these
  533. case Index of
  534. INDEX_RESP_Date : Result := ToGMT(FResponseInfo.Date);
  535. INDEX_RESP_Expires : Result := ToGMT(FResponseInfo.Expires);
  536. INDEX_RESP_LastModified : Result := ToGMT(FResponseInfo.LastModified);
  537. else
  538. raise EWBBInvalidIdxGetDateVariable.Create( Format( RSWBBInvalidIdxGetDateVariable,[inttostr(Index)]));
  539. end;
  540. end;
  541. procedure TIdHTTPAppResponse.SetDateVariable(Index: Integer; const Value: TDateTime);
  542. // WebBroker apps are responsible for conversion to GMT, Indy HTTP server expects apps to pas local time
  543. function ToLocal(ADateTime: TDateTime): TDateTime;
  544. begin
  545. Result := ADateTime;
  546. if Result <> -1 then begin
  547. Result := UTCTimeToLocalTime(Result);
  548. end;
  549. end;
  550. begin
  551. //TODO: resource string these
  552. case Index of
  553. INDEX_RESP_Date : FResponseInfo.Date := ToLocal(Value);
  554. INDEX_RESP_Expires : FResponseInfo.Expires := ToLocal(Value);
  555. INDEX_RESP_LastModified : FResponseInfo.LastModified := ToLocal(Value);
  556. else
  557. raise EWBBInvalidIdxSetDateVariable.Create(Format(RSWBBInvalidIdxSetDateVariable,[inttostr(Index) ]));
  558. end;
  559. end;
  560. function TIdHTTPAppResponse.GetIntegerVariable(Index: Integer): {$IFDEF WBB_BIG_INTS}Int64{$ELSE}Integer{$ENDIF};
  561. begin
  562. //TODO: resource string these
  563. case Index of
  564. INDEX_RESP_ContentLength: Result := FResponseInfo.ContentLength;
  565. else
  566. raise EWBBInvalidIdxGetIntVariable.Create( Format( RSWBBInvalidIdxGetIntVariable,[inttostr(Index)]));
  567. end;
  568. end;
  569. procedure TIdHTTPAppResponse.SetIntegerVariable(Index: Integer;
  570. Value: {$IFDEF WBB_BIG_INTS}Int64{$ELSE}Integer{$ENDIF});
  571. begin
  572. //TODO: resource string these
  573. case Index of
  574. INDEX_RESP_ContentLength: FResponseInfo.ContentLength := Value;
  575. else
  576. raise EWBBInvalidIdxSetIntVariable.Create( Format(RSWBBInvalidIdxSetIntVariable,[inttostr(Index)])); {do not localize}
  577. end;
  578. end;
  579. function TIdHTTPAppResponse.GetStringVariable(Index: Integer): {$IFDEF WBB_ANSI}AnsiString{$ELSE}string{$ENDIF};
  580. var
  581. LValue: string;
  582. begin
  583. // RLebeau 1/15/2016: On Delphi versions prior to 10.1 Berlin, this method
  584. // returned an AnsiString, even in Unicode versions. So as not to have to
  585. // IFDEF all of these fields, now doing one conversion at the end of this
  586. // method, which means having a local String variable...
  587. //TODO: resource string these
  588. case Index of
  589. INDEX_RESP_Version : LValue := FRequestInfo.Version;
  590. INDEX_RESP_ReasonString : LValue := FResponseInfo.ResponseText;
  591. INDEX_RESP_Server : LValue := FResponseInfo.Server;
  592. INDEX_RESP_WWWAuthenticate : LValue := FResponseInfo.WWWAuthenticate.Text;
  593. INDEX_RESP_Realm : LValue := FResponseInfo.AuthRealm;
  594. INDEX_RESP_Allow : LValue := FResponseInfo.CustomHeaders.Values['Allow']; {do not localize}
  595. INDEX_RESP_Location : LValue := FResponseInfo.Location;
  596. INDEX_RESP_ContentEncoding : LValue := FResponseInfo.ContentEncoding;
  597. INDEX_RESP_ContentType :
  598. begin
  599. if FContentType <> '' then begin
  600. Result := FContentType;
  601. Exit;
  602. end;
  603. LValue := FResponseInfo.ContentType;
  604. end;
  605. INDEX_RESP_ContentVersion : LValue := FResponseInfo.ContentVersion;
  606. INDEX_RESP_DerivedFrom : LValue := FResponseInfo.CustomHeaders.Values['Derived-From']; {do not localize}
  607. INDEX_RESP_Title : LValue := FResponseInfo.CustomHeaders.Values['Title']; {do not localize}
  608. else
  609. raise EWBBInvalidIdxGetStrVariable.Create(Format(RSWBBInvalidIdxGetStrVariable,[ IntToStr(Index)]));
  610. end;
  611. Result := {$IFDEF WBB_ANSI}AnsiString(LValue){$ELSE}LValue{$ENDIF};
  612. end;
  613. procedure TIdHTTPAppResponse.SetStringVariable(Index: Integer; const Value: {$IFDEF WBB_ANSI}AnsiString{$ELSE}string{$ENDIF});
  614. var
  615. LValue: string;
  616. begin
  617. LValue := {$IFDEF WBB_ANSI}string(Value){$ELSE}Value{$ENDIF};
  618. //TODO: resource string these
  619. case Index of
  620. INDEX_RESP_Version : EWBBInvalidStringVar.Create(RSWBBInvalidStringVar); // RLebeau: shouldn't this be calling 'raise'?
  621. INDEX_RESP_ReasonString : FResponseInfo.ResponseText := LValue;
  622. INDEX_RESP_Server : FResponseInfo.Server := LValue;
  623. INDEX_RESP_WWWAuthenticate : FResponseInfo.WWWAuthenticate.Text := LValue;
  624. INDEX_RESP_Realm : FResponseInfo.AuthRealm := LValue;
  625. INDEX_RESP_Allow : FResponseInfo.CustomHeaders.Values['Allow'] := LValue; {do not localize}
  626. INDEX_RESP_Location : FResponseInfo.Location := LValue;
  627. INDEX_RESP_ContentEncoding : FResponseInfo.ContentEncoding := LValue;
  628. INDEX_RESP_ContentType :
  629. begin
  630. FResponseInfo.ContentType := LValue;
  631. FContentType := Value; // using the original input variable, not the converted local variable
  632. end;
  633. INDEX_RESP_ContentVersion : FResponseInfo.ContentVersion := LValue;
  634. INDEX_RESP_DerivedFrom : FResponseInfo.CustomHeaders.Values['Derived-From'] := LValue; {do not localize}
  635. INDEX_RESP_Title : FResponseInfo.CustomHeaders.Values['Title'] := LValue; {do not localize}
  636. else
  637. raise EWBBInvalidIdxSetStringVar.Create( Format(RSWBBInvalidIdxSetStringVar,[IntToStr(Index)])); {do not localize}
  638. end;
  639. end;
  640. procedure TIdHTTPAppResponse.SendRedirect(const URI: {$IFDEF WBB_ANSI}AnsiString{$ELSE}string{$ENDIF});
  641. begin
  642. FSent := True;
  643. MoveCookiesAndCustomHeaders;
  644. FResponseInfo.Redirect({$IFDEF WBB_ANSI}string(URI){$ELSE}URI{$ENDIF});
  645. end;
  646. procedure TIdHTTPAppResponse.SendResponse;
  647. begin
  648. FSent := True;
  649. // Reset to -1 so Indy will auto set it
  650. FResponseInfo.ContentLength := -1;
  651. MoveCookiesAndCustomHeaders;
  652. {$IFDEF DCC_10_1_OR_ABOVE}
  653. // TODO: This code may not be in the correct location.
  654. if (FResponseInfo.ContentType = '') and
  655. ((FResponseInfo.ContentText <> '') or (Assigned(FResponseInfo.ContentStream))) and
  656. (HTTPApp.DefaultCharSet <> '') then
  657. begin
  658. // Indicate how to convert UTF16 when write.
  659. ContentType := Format('text/html; charset=%s', [HTTPApp.DefaultCharSet]); {Do not Localize}
  660. end;
  661. {$ENDIF}
  662. FResponseInfo.WriteContent;
  663. end;
  664. procedure TIdHTTPAppResponse.SendStream(AStream: TStream);
  665. begin
  666. FThread.Connection.IOHandler.Write(AStream);
  667. end;
  668. function TIdHTTPAppResponse.Sent: Boolean;
  669. begin
  670. Result := FSent;
  671. end;
  672. procedure TIdHTTPAppResponse.SetContent(const AValue: {$IFDEF WBB_ANSI}AnsiString{$ELSE}string{$ENDIF});
  673. {$IFDEF WBB_ANSI}
  674. var
  675. LValue : string;
  676. {$ENDIF}
  677. begin
  678. {$IFDEF WBB_ANSI}
  679. // AValue contains Encoded bytes
  680. if AValue <> '' then begin
  681. // RLebeau 3/28/2013: decode the content using the specified charset.
  682. if FResponseInfo.CharSet <> '' then begin
  683. LValue := CharsetToEncoding(FResponseInfo.CharSet).GetString(PByte(PAnsiChar(AValue)), Length(AValue));
  684. end else begin
  685. LValue := string(AValue);
  686. end;
  687. end;
  688. FResponseInfo.ContentText := LValue;
  689. // TODO: use Length(AValue) instead, as the ContentText *should* get re-encoded
  690. // back to the same value as AValue when transmitted. Or, just set ContentLength
  691. // to -1 and let Indy calculate it later...
  692. FResponseInfo.ContentLength := Length(LValue);
  693. {$ELSE}
  694. FResponseInfo.ContentText := AValue;
  695. FResponseInfo.ContentLength := Length(AValue);
  696. {$ENDIF}
  697. end;
  698. procedure TIdHTTPAppResponse.SetLogMessage(const Value: string);
  699. begin
  700. // logging not supported
  701. end;
  702. procedure TIdHTTPAppResponse.SetStatusCode(AValue: Integer);
  703. begin
  704. FResponseInfo.ResponseNo := AValue;
  705. end;
  706. procedure TIdHTTPAppResponse.SetContentStream(AValue: TStream);
  707. begin
  708. inherited;
  709. FResponseInfo.ContentStream := AValue;
  710. end;
  711. function DoHTTPEncode(const AStr: {$IFDEF WBB_ANSI}AnsiString{$ELSE}string{$ENDIF}): String;
  712. begin
  713. {$IFDEF HAS_TNetEncoding}
  714. Result := TNetEncoding.URL.Encode(string(AStr));
  715. {$ELSE}
  716. Result := String(HTTPEncode(AStr));
  717. {$ENDIF}
  718. end;
  719. procedure TIdHTTPAppResponse.MoveCookiesAndCustomHeaders;
  720. var
  721. i: Integer;
  722. LSrcCookie: TCookie;
  723. LDestCookie: TIdCookie;
  724. begin
  725. for i := 0 to Cookies.Count - 1 do begin
  726. LSrcCookie := Cookies[i];
  727. LDestCookie := FResponseInfo.Cookies.Add;
  728. LDestCookie.CookieName := DoHTTPEncode(LSrcCookie.Name);
  729. LDestCookie.Value := DoHTTPEncode(LSrcCookie.Value);
  730. LDestCookie.Domain := String(LSrcCookie.Domain);
  731. LDestCookie.Path := String(LSrcCookie.Path);
  732. LDestCookie.Expires := LSrcCookie.Expires;
  733. LDestCookie.Secure := LSrcCookie.Secure;
  734. {$IFDEF DCC_10_2_OR_ABOVE}
  735. LDestCookie.HttpOnly := LSrcCookie.HttpOnly;
  736. {$ENDIF}
  737. {$IFDEF DCC_10_4_UPDATE2_OR_ABOVE}
  738. LDestCookie.SameSite := LSrcCookie.SameSite;
  739. {$ENDIF}
  740. end;
  741. FResponseInfo.CustomHeaders.Clear;
  742. FResponseInfo.CustomHeaders.AddStdValues(CustomHeaders);
  743. end;
  744. { TIdHTTPWebBrokerBridge }
  745. constructor TIdHTTPWebBrokerBridge.Create(AOwner: TComponent);
  746. begin
  747. inherited Create(AOwner);
  748. // FOkToProcessCommand := True;
  749. end;
  750. procedure TIdHTTPWebBrokerBridge.DoCommandOther(AThread: TIdContext;
  751. ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
  752. begin
  753. DoCommandGet(AThread, ARequestInfo, AResponseInfo);
  754. end;
  755. type
  756. TIdHTTPWebBrokerBridgeRequestHandler = class(TWebRequestHandler)
  757. {$IFDEF HAS_CLASSVARS}
  758. private
  759. class var FWebRequestHandler: TIdHTTPWebBrokerBridgeRequestHandler;
  760. {$ENDIF}
  761. public
  762. constructor Create(AOwner: TComponent); override;
  763. {$IF DEFINED(HAS_CLASSVARS) AND DEFINED(HAS_CLASSDESTRUCTOR)}
  764. class destructor Destroy;
  765. {$IFEND}
  766. destructor Destroy; override;
  767. procedure Run(AThread: TIdContext; ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
  768. end;
  769. {$IFNDEF HAS_CLASSVARS}
  770. var
  771. IndyWebRequestHandler: TIdHTTPWebBrokerBridgeRequestHandler = nil;
  772. {$ENDIF}
  773. { TIdHTTPWebBrokerBridgeRequestHandler }
  774. procedure TIdHTTPWebBrokerBridgeRequestHandler.Run(AThread: TIdContext; ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
  775. var
  776. LRequest: TIdHTTPAppRequest;
  777. LResponse: TIdHTTPAppResponse;
  778. begin
  779. try
  780. LRequest := TIdHTTPAppRequest.Create(AThread, ARequestInfo, AResponseInfo);
  781. try
  782. LResponse := TIdHTTPAppResponse.Create(LRequest, AThread, ARequestInfo, AResponseInfo);
  783. try
  784. // WebBroker will free it and we cannot change this behaviour
  785. AResponseInfo.FreeContentStream := False;
  786. HandleRequest(LRequest, LResponse);
  787. finally
  788. LResponse.Free;
  789. end;
  790. finally
  791. LRequest.Free;
  792. end;
  793. except
  794. // Let Indy handle this exception
  795. raise;
  796. end;
  797. end;
  798. constructor TIdHTTPWebBrokerBridgeRequestHandler.Create(AOwner: TComponent);
  799. begin
  800. inherited;
  801. Classes.ApplicationHandleException := HandleException;
  802. end;
  803. destructor TIdHTTPWebBrokerBridgeRequestHandler.Destroy;
  804. begin
  805. Classes.ApplicationHandleException := nil;
  806. inherited;
  807. end;
  808. {$IF DEFINED(HAS_CLASSVARS) AND DEFINED(HAS_CLASSDESTRUCTOR)}
  809. class destructor TIdHTTPWebBrokerBridgeRequestHandler.Destroy;
  810. begin
  811. FWebRequestHandler.Free;
  812. end;
  813. {$IFEND}
  814. function IdHTTPWebBrokerBridgeRequestHandler: TWebRequestHandler;
  815. begin
  816. {$IFDEF HAS_CLASSVARS}
  817. if not Assigned(TIdHTTPWebBrokerBridgeRequestHandler.FWebRequestHandler) then
  818. TIdHTTPWebBrokerBridgeRequestHandler.FWebRequestHandler := TIdHTTPWebBrokerBridgeRequestHandler.Create(nil);
  819. Result := TIdHTTPWebBrokerBridgeRequestHandler.FWebRequestHandler;
  820. {$ELSE}
  821. if not Assigned(IndyWebRequestHandler) then
  822. IndyWebRequestHandler := TIdHTTPWebBrokerBridgeRequestHandler.Create(nil);
  823. Result := IndyWebRequestHandler;
  824. {$ENDIF}
  825. end;
  826. procedure TIdHTTPWebBrokerBridge.DoCommandGet(AThread: TIdContext;
  827. ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
  828. begin
  829. if FWebModuleClass <> nil then begin
  830. // FWebModuleClass, RegisterWebModuleClass supported for backward compatability
  831. RunWebModuleClass(AThread, ARequestInfo, AResponseInfo)
  832. end else
  833. begin
  834. {$IFDEF HAS_CLASSVARS}
  835. TIdHTTPWebBrokerBridgeRequestHandler.FWebRequestHandler.Run(AThread, ARequestInfo, AResponseInfo);
  836. {$ELSE}
  837. IndyWebRequestHandler.Run(AThread, ARequestInfo, AResponseInfo);
  838. {$ENDIF}
  839. end;
  840. end;
  841. procedure TIdHTTPWebBrokerBridge.RunWebModuleClass(AThread: TIdContext;
  842. ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
  843. var
  844. LRequest: TIdHTTPAppRequest;
  845. LResponse: TIdHTTPAppResponse;
  846. LWebModule: TCustomWebDispatcher;
  847. WebRequestHandler: IWebRequestHandler;
  848. Handled: Boolean;
  849. begin
  850. LRequest := TIdHTTPAppRequest.Create(AThread, ARequestInfo, AResponseInfo);
  851. try
  852. LResponse := TIdHTTPAppResponse.Create(LRequest, AThread, ARequestInfo, AResponseInfo);
  853. try
  854. // WebBroker will free it and we cannot change this behaviour
  855. AResponseInfo.FreeContentStream := False;
  856. // There are better ways in D6, but this works in D5
  857. LWebModule := FWebModuleClass.Create(nil) as TCustomWebDispatcher;
  858. try
  859. if Supports(LWebModule, IWebRequestHandler, WebRequestHandler) then begin
  860. try
  861. Handled := WebRequestHandler.HandleRequest(LRequest, LResponse);
  862. finally
  863. WebRequestHandler := nil;
  864. end;
  865. end else begin
  866. {$I IdObjectChecksOff.inc}
  867. Handled := TWebDispatcherAccess(LWebModule).DispatchAction(LRequest, LResponse);
  868. {$I IdObjectChecksOn.inc}
  869. end;
  870. if Handled and (not LResponse.Sent) then begin
  871. LResponse.SendResponse;
  872. end;
  873. finally
  874. LWebModule.Free;
  875. end;
  876. finally
  877. LResponse.Free;
  878. end;
  879. finally
  880. LRequest.Free;
  881. end;
  882. end;
  883. // FWebModuleClass, RegisterWebModuleClass supported for backward compatability
  884. // Instead set WebModuleClass using: WebReq.WebRequestHandler.WebModuleClass := TWebModule1;
  885. procedure TIdHTTPWebBrokerBridge.RegisterWebModuleClass(AClass: TComponentClass);
  886. begin
  887. FWebModuleClass := AClass;
  888. end;
  889. initialization
  890. WebReq.WebRequestHandlerProc := IdHTTPWebBrokerBridgeRequestHandler;
  891. {$IFDEF HAS_CLASSVARS}
  892. {$IFNDEF HAS_CLASSDESTRUCTOR}
  893. finalization
  894. FreeAndNil(TIdHTTPWebBrokerBridgeRequestHandler.FWebRequestHandler);
  895. {$ENDIF}
  896. {$ELSE}
  897. finalization
  898. FreeAndNil(IndyWebRequestHandler);
  899. {$ENDIF}
  900. end.