IdCustomHTTPServer.pas 94 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645
  1. {
  2. $Project$
  3. $Workfile$
  4. $Revision$
  5. $DateUTC$
  6. $Id$
  7. This file is part of the Indy (Internet Direct) project, and is offered
  8. under the dual-licensing agreement described on the Indy website.
  9. (http://www.indyproject.org/)
  10. Copyright:
  11. (c) 1993-2005, Chad Z. Hower and the Indy Pit Crew. All rights reserved.
  12. }
  13. {
  14. $Log$
  15. }
  16. {
  17. Rev 1.42 3/14/05 11:45:50 AM RLebeau
  18. Buf fix for DoExecute() not not filling in the TIdHTTPRequestInfo.FormParams
  19. correctly.
  20. Removed LImplicitPostStream variable from DoExecute(), no longer used.
  21. TIdHTTPRequestInfo takes ownership of the PostStream anyway, so no need to
  22. free it early. This also allows the PostStream to always be available in the
  23. OnCommand... event handlers.
  24. Rev 1.41 2/9/05 2:11:02 AM RLebeau
  25. Removed compiler hint
  26. Rev 1.40 2/9/05 1:19:26 AM RLebeau
  27. Fixes for Compiler errors
  28. Rev 1.39 2/8/05 6:47:46 PM RLebeau
  29. updated OnCommandOther to have ARequestInfo and AResponseInfo parameters
  30. Rev 1.38 12/16/04 2:15:20 AM RLebeau
  31. Another DoExecute() update
  32. Rev 1.37 12/15/04 9:03:50 PM RLebeau
  33. Renamed TIdHTTPRequestInfo.DecodeCommand() to DecodeHTTPCommand() and made it
  34. into a standalone function.
  35. Rev 1.36 12/15/04 4:17:42 PM RLebeau
  36. Updated DoExecute() to call LRequestInfo.DecodeCommand()
  37. Rev 1.35 12/2/2004 4:23:48 PM JPMugaas
  38. Adjusted for changes in Core.
  39. Rev 1.34 10/26/2004 8:59:32 PM JPMugaas
  40. Updated with new TStrings references for more portability.
  41. Rev 1.33 2004.05.20 11:37:12 AM czhower
  42. IdStreamVCL
  43. Rev 1.32 5/6/04 3:19:00 PM RLebeau
  44. Added extra comments
  45. Rev 1.31 2004.04.18 12:52:06 AM czhower
  46. Big bug fix with server disconnect and several other bug fixed that I found
  47. along the way.
  48. Rev 1.30 2004.04.08 1:46:32 AM czhower
  49. Small Optimizations
  50. Rev 1.29 7/4/2004 4:10:44 PM SGrobety
  51. Small fix to keep it synched with the IOHandler properties
  52. Rev 1.28 6/4/2004 5:15:02 PM SGrobety
  53. Implemented MaximumHeaderLineCount property (default to 1024)
  54. Rev 1.27 2004.02.03 5:45:02 PM czhower
  55. Name changes
  56. Rev 1.26 1/27/2004 3:58:52 PM SPerry
  57. StringStream ->IdStringStream
  58. Rev 1.25 2004.01.22 5:58:58 PM czhower
  59. IdCriticalSection
  60. Rev 1.24 1/22/2004 8:26:28 AM JPMugaas
  61. Ansi* calls changed.
  62. Rev 1.23 1/21/2004 1:57:30 PM JPMugaas
  63. InitComponent
  64. Rev 1.22 21.1.2004 ã. 13:22:18 DBondzhev
  65. Fix for Dccil bug
  66. Rev 1.21 10/25/2003 06:51:44 AM JPMugaas
  67. Updated for new API changes and tried to restore some functionality.
  68. Rev 1.20 2003.10.24 10:43:02 AM czhower
  69. TIdSTream to dos
  70. Rev 1.19 10/19/2003 11:49:40 AM DSiders
  71. Added localization comments.
  72. Rev 1.18 10/17/2003 12:05:40 AM DSiders
  73. Corrected spelling error in resource string.
  74. Rev 1.17 10/15/2003 11:10:16 PM GGrieve
  75. DotNet changes
  76. Rev 1.16 2003.10.12 3:37:58 PM czhower
  77. Now compiles again.
  78. Rev 1.15 6/24/2003 11:38:50 AM BGooijen
  79. Fixed ssl support
  80. Rev 1.14 6/18/2003 11:44:04 PM BGooijen
  81. Moved ServeFile and SmartServeFile to TIdHTTPResponseInfo.
  82. Added TIdHTTPResponseInfo.HTTPServer field
  83. Rev 1.13 05.6.2003 ã. 11:11:12 DBondzhev
  84. Socket exceptions should not be stopped after DoCommandGet.
  85. Rev 1.12 4/9/2003 9:38:40 PM BGooijen
  86. fixed av on FSessionList.PurgeStaleSessions(Terminated);
  87. Rev 1.11 20/3/2003 19:49:24 GGrieve
  88. Define SmartServeFile
  89. Rev 1.10 3/13/2003 10:21:14 AM BGooijen
  90. Changed result of function .execute
  91. Rev 1.9 2/25/2003 10:43:36 AM BGooijen
  92. removed unneeded assignment
  93. Rev 1.8 2/25/2003 10:38:46 AM BGooijen
  94. The Serversoftware wasn't send to the client, because of duplicate properties
  95. (.Server and .ServerSoftware).
  96. Rev 1.7 2/24/2003 08:20:50 PM JPMugaas
  97. Now should compile with new code.
  98. Rev 1.6 11.2.2003 13:36:14 TPrami
  99. - Fixed URL get paremeter handling (SeeRFC 1866 section 8.2.1.)
  100. Rev 1.5 1/17/2003 05:35:20 PM JPMugaas
  101. Now compiles with new design.
  102. Rev 1.4 1-1-2003 20:12:44 BGooijen
  103. Changed to support the new TIdContext class
  104. Rev 1.3 12-15-2002 13:08:38 BGooijen
  105. simplified TimeStampInterval
  106. Rev 1.2 6/12/2002 10:59:34 AM SGrobety Version: 1.1
  107. Made to work with Indy 10
  108. Rev 1.0 21/11/2002 12:41:04 PM SGrobety Version: Indy 10
  109. Rev 1.0 11/14/2002 02:16:32 PM JPMugaas
  110. }
  111. unit IdCustomHTTPServer;
  112. interface
  113. {$i IdCompilerDefines.inc}
  114. uses
  115. Classes,
  116. {$IFDEF HAS_UNIT_Generics_Collections}
  117. System.Generics.Collections,
  118. {$ENDIF}
  119. IdAssignedNumbers,
  120. IdContext, IdException,
  121. IdGlobal, IdStack,
  122. IdExceptionCore, IdGlobalProtocols, IdHeaderList, IdCustomTCPServer,
  123. IdTCPConnection, IdThread, IdCookie, IdHTTPHeaderInfo, IdStackConsts,
  124. IdBaseComponent, IdThreadSafe,
  125. SysUtils;
  126. type
  127. // Enums
  128. THTTPCommandType = (hcUnknown, hcHEAD, hcGET, hcPOST, hcDELETE, hcPUT, hcTRACE, hcOPTION, hcPATCH);
  129. const
  130. Id_TId_HTTPServer_KeepAlive = false;
  131. Id_TId_HTTPServer_KeepAliveMaxRequests = 0;
  132. Id_TId_HTTPServer_KeepAliveTimeout = 0;
  133. Id_TId_HTTPServer_ParseParams = True;
  134. Id_TId_HTTPServer_SessionState = False;
  135. Id_TId_HTTPSessionTimeOut = 0;
  136. Id_TId_HTTPAutoStartSession = False;
  137. Id_TId_HTTPMaximumHeaderLineCount = 1024;
  138. GResponseNo = 200;
  139. GFContentLength = -1;
  140. GServerSoftware = gsIdProductName + '/' + gsIdVersion; {Do not Localize}
  141. GContentType = 'text/html'; {Do not Localize}
  142. GSessionIDCookie = 'IDHTTPSESSIONID'; {Do not Localize}
  143. HTTPRequestStrings: array[0..Ord(High(THTTPCommandType))] of string = ('UNKNOWN', 'HEAD','GET','POST','DELETE','PUT','TRACE', 'OPTIONS', 'PATCH'); {do not localize}
  144. type
  145. // Forwards
  146. TIdHTTPSession = class;
  147. TIdHTTPCustomSessionList = class;
  148. TIdHTTPRequestInfo = class;
  149. TIdHTTPResponseInfo = class;
  150. TIdCustomHTTPServer = class;
  151. //events
  152. TIdHTTPSessionEndEvent = procedure(Sender: TIdHTTPSession) of object;
  153. TIdHTTPSessionStartEvent = procedure(Sender: TIdHTTPSession) of object;
  154. TIdHTTPCreateSession = procedure(ASender:TIdContext;
  155. var VHTTPSession: TIdHTTPSession) of object;
  156. TIdHTTPCreatePostStream = procedure(AContext: TIdContext; AHeaders: TIdHeaderList; var VPostStream: TStream) of object;
  157. TIdHTTPDoneWithPostStream = procedure(AContext: TIdContext; ARequestInfo: TIdHTTPRequestInfo; var VCanFree: Boolean) of object;
  158. TIdHTTPParseAuthenticationEvent = procedure(AContext: TIdContext; const AAuthType, AAuthData: String; var VUsername, VPassword: String; var VHandled: Boolean) of object;
  159. TIdHTTPCommandEvent = procedure(AContext: TIdContext;
  160. ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo) of object;
  161. TIdHTTPCommandError = procedure(AContext: TIdContext;
  162. ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo;
  163. AException: Exception) of object;
  164. TIdHTTPInvalidSessionEvent = procedure(AContext: TIdContext;
  165. ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo;
  166. var VContinueProcessing: Boolean; const AInvalidSessionID: String) of object;
  167. TIdHTTPHeadersAvailableEvent = procedure(AContext: TIdContext; const AUri: string; AHeaders: TIdHeaderList; var VContinueProcessing: Boolean) of object;
  168. TIdHTTPHeadersBlockedEvent = procedure(AContext: TIdContext; AHeaders: TIdHeaderList; var VResponseNo: Integer; var VResponseText, VContentText: String) of object;
  169. TIdHTTPHeaderExpectationsEvent = procedure(AContext: TIdContext; const AExpectations: String; var VContinueProcessing: Boolean) of object;
  170. TIdHTTPQuerySSLPortEvent = procedure(APort: TIdPort; var VUseSSL: Boolean) of object;
  171. //objects
  172. EIdHTTPServerError = class(EIdException);
  173. EIdHTTPHeaderAlreadyWritten = class(EIdHTTPServerError);
  174. EIdHTTPErrorParsingCommand = class(EIdHTTPServerError);
  175. EIdHTTPUnsupportedAuthorisationScheme = class(EIdHTTPServerError);
  176. EIdHTTPCannotSwitchSessionStateWhenActive = class(EIdHTTPServerError);
  177. EIdHTTPCannotSwitchSessionIDCookieNameWhenActive = class(EIdHTTPServerError);
  178. TIdHTTPRequestInfo = class(TIdRequestHeaderInfo)
  179. protected
  180. FAuthExists: Boolean;
  181. FCookies: TIdCookies;
  182. FParams: TStrings;
  183. FPostStream: TStream;
  184. FRawHTTPCommand: string;
  185. FRemoteIP: string;
  186. FSession: TIdHTTPSession;
  187. FDocument: string;
  188. FURI: string;
  189. FCommand: string;
  190. FVersion: string;
  191. FVersionMajor: Integer;
  192. FVersionMinor: Integer;
  193. FAuthUsername: string;
  194. FAuthPassword: string;
  195. FUnparsedParams: string;
  196. FQueryParams: string;
  197. FFormParams: string;
  198. FCommandType: THTTPCommandType;
  199. FAuthType: string;
  200. FNumRequestsAllowed: Integer;
  201. //
  202. procedure DecodeAndSetParams(const AValue: String); virtual;
  203. public
  204. constructor Create(AOwner: TPersistent); override;
  205. destructor Destroy; override;
  206. //
  207. function IsVersionAtLeast(const AMajor, AMinor: Integer): Boolean;
  208. property Session: TIdHTTPSession read FSession;
  209. //
  210. property AuthExists: Boolean read FAuthExists;
  211. property AuthType: string read FAuthType;
  212. property AuthPassword: string read FAuthPassword;
  213. property AuthUsername: string read FAuthUsername;
  214. property Command: string read FCommand;
  215. property CommandType: THTTPCommandType read FCommandType;
  216. property Cookies: TIdCookies read FCookies;
  217. property Document: string read FDocument write FDocument; // writable for isapi compatibility. Use with care
  218. property URI: string read FURI;
  219. property Params: TStrings read FParams;
  220. property PostStream: TStream read FPostStream write FPostStream;
  221. property RawHTTPCommand: string read FRawHTTPCommand;
  222. property RemoteIP: String read FRemoteIP;
  223. property UnparsedParams: string read FUnparsedParams write FUnparsedParams; // writable for isapi compatibility. Use with care
  224. property FormParams: string read FFormParams write FFormParams; // writable for isapi compatibility. Use with care
  225. property QueryParams: string read FQueryParams write FQueryParams; // writable for isapi compatibility. Use with care
  226. property Version: string read FVersion;
  227. property VersionMajor: Integer read FVersionMajor;
  228. property VersionMinor: Integer read FVersionMinor;
  229. end;
  230. TIdHTTPResponseInfo = class(TIdResponseHeaderInfo)
  231. protected
  232. FAuthRealm: string;
  233. FAuthCharset: string;
  234. FConnection: TIdTCPConnection;
  235. FResponseNo: Integer;
  236. FCookies: TIdCookies;
  237. FContentStream: TStream;
  238. FContentText: string;
  239. FCloseConnection: Boolean;
  240. FFreeContentStream: Boolean;
  241. FHeaderHasBeenWritten: Boolean;
  242. FResponseText: string;
  243. FHTTPServer: TIdCustomHTTPServer;
  244. FSession: TIdHTTPSession;
  245. FRequestInfo: TIdHTTPRequestInfo;
  246. //
  247. procedure ReleaseContentStream;
  248. procedure SetCookies(const AValue: TIdCookies);
  249. procedure SetHeaders; override;
  250. procedure SetResponseNo(const AValue: Integer);
  251. procedure SetCloseConnection(const Value: Boolean);
  252. public
  253. function GetServer: string;
  254. procedure SetServer(const Value: string);
  255. public
  256. procedure CloseSession;
  257. constructor Create(AServer: TIdCustomHTTPServer; ARequestInfo: TIdHTTPRequestInfo; AConnection: TIdTCPConnection); reintroduce;
  258. destructor Destroy; override;
  259. procedure Redirect(const AURL: string);
  260. procedure WriteHeader;
  261. procedure WriteContent;
  262. //
  263. function ServeFile(AContext: TIdContext; const AFile: String): Int64; virtual;
  264. function SmartServeFile(AContext: TIdContext; const AFile: String): Int64; overload;
  265. function SmartServeFile(AContext: TIdContext; ARequestInfo: TIdHTTPRequestInfo; const AFile: String): Int64; overload; deprecated 'Use SmartServeFile() without ARequestInfo parameter';
  266. //
  267. property AuthCharset: string read FAuthCharset write FAuthCharset;
  268. property AuthRealm: string read FAuthRealm write FAuthRealm;
  269. property CloseConnection: Boolean read FCloseConnection write SetCloseConnection;
  270. property ContentStream: TStream read FContentStream write FContentStream;
  271. property ContentText: string read FContentText write FContentText;
  272. property Cookies: TIdCookies read FCookies write SetCookies;
  273. property FreeContentStream: Boolean read FFreeContentStream write FFreeContentStream;
  274. // writable for isapi compatibility. Use with care
  275. property HeaderHasBeenWritten: Boolean read FHeaderHasBeenWritten write FHeaderHasBeenWritten;
  276. property RequestInfo: TIdHTTPRequestInfo read FRequestInfo;
  277. property ResponseNo: Integer read FResponseNo write SetResponseNo;
  278. property ResponseText: String read FResponseText write FResponseText;
  279. property HTTPServer: TIdCustomHTTPServer read FHTTPServer;
  280. property ServerSoftware: string read GetServer write SetServer;
  281. property Session: TIdHTTPSession read FSession;
  282. end;
  283. TIdHTTPSession = Class(TObject)
  284. protected
  285. FContent: TStrings;
  286. FLastTimeStamp: TDateTime;
  287. FLock: TIdCriticalSection;
  288. {$IF DEFINED(HAS_UNSAFE_OBJECT_REF}[Unsafe]
  289. {$ELSEIF DEFINED(HAS_WEAK_OBJECT_REF}[Weak]
  290. {$IFEND} FOwner: TIdHTTPCustomSessionList;
  291. FSessionID: string;
  292. FRemoteHost: string;
  293. //
  294. procedure SetContent(const Value: TStrings);
  295. function IsSessionStale: boolean; virtual;
  296. procedure DoSessionEnd; virtual;
  297. public
  298. constructor Create(AOwner: TIdHTTPCustomSessionList); virtual;
  299. constructor CreateInitialized(AOwner: TIdHTTPCustomSessionList; const SessionID,
  300. RemoteIP: string); virtual;
  301. destructor Destroy; override;
  302. procedure Lock;
  303. procedure Unlock;
  304. //
  305. property Content: TStrings read FContent write SetContent;
  306. property LastTimeStamp: TDateTime read FLastTimeStamp;
  307. property RemoteHost: string read FRemoteHost;
  308. property SessionID: String read FSessionID;
  309. end;
  310. {$IFDEF HAS_GENERICS_TThreadList}
  311. TIdHTTPSessionThreadList = TThreadList<TIdHTTPSession>;
  312. TIdHTTPSessionList = TList<TIdHTTPSession>;
  313. {$ELSE}
  314. // TODO: flesh out to match TThreadList<TIdHTTPSession> and TList<TIdHTTPSession> for non-Generics compilers
  315. TIdHTTPSessionThreadList = TThreadList;
  316. TIdHTTPSessionList = TList;
  317. {$ENDIF}
  318. TIdHTTPCustomSessionList = class(TIdBaseComponent)
  319. private
  320. FSessionTimeout: Integer;
  321. FOnSessionEnd: TIdHTTPSessionEndEvent;
  322. FOnSessionStart: TIdHTTPSessionStartEvent;
  323. protected
  324. // remove a session from the session list. Called by the session on "Free"
  325. procedure RemoveSession(Session: TIdHTTPSession); virtual; abstract;
  326. public
  327. procedure Clear; virtual; abstract;
  328. procedure PurgeStaleSessions(PurgeAll: Boolean = false); virtual; abstract;
  329. function CreateUniqueSession(const RemoteIP: String): TIdHTTPSession; virtual; abstract;
  330. function CreateSession(const RemoteIP, SessionID: String): TIdHTTPSession; virtual; abstract;
  331. function GetSession(const SessionID, RemoteIP: string): TIdHTTPSession; virtual; abstract;
  332. procedure Add(ASession: TIdHTTPSession); virtual; Abstract;
  333. published
  334. property SessionTimeout: Integer read FSessionTimeout write FSessionTimeout;
  335. property OnSessionEnd: TIdHTTPSessionEndEvent read FOnSessionEnd write FOnSessionEnd;
  336. property OnSessionStart: TIdHTTPSessionStartEvent read FOnSessionStart write FOnSessionStart;
  337. end;
  338. TIdThreadSafeMimeTable = class(TIdThreadSafe)
  339. protected
  340. FTable: TIdMimeTable;
  341. function GetLoadTypesFromOS: Boolean;
  342. procedure SetLoadTypesFromOS(AValue: Boolean);
  343. function GetOnBuildCache: TNotifyEvent;
  344. procedure SetOnBuildCache(AValue: TNotifyEvent);
  345. public
  346. constructor Create(const AutoFill: Boolean = True); reintroduce;
  347. destructor Destroy; override;
  348. procedure BuildCache;
  349. procedure AddMimeType(const Ext, MIMEType: string; const ARaiseOnError: Boolean = True);
  350. function GetFileMIMEType(const AFileName: string): string;
  351. function GetDefaultFileExt(const MIMEType: string): string;
  352. procedure LoadFromStrings(const AStrings: TStrings; const MimeSeparator: Char = '='); {Do not Localize}
  353. procedure SaveToStrings(const AStrings: TStrings; const MimeSeparator: Char = '='); {Do not Localize}
  354. function Lock: TIdMimeTable; reintroduce;
  355. procedure Unlock; reintroduce;
  356. //
  357. property LoadTypesFromOS: Boolean read GetLoadTypesFromOS write SetLoadTypesFromOS;
  358. property OnBuildCache: TNotifyEvent read GetOnBuildCache write SetOnBuildCache;
  359. end;
  360. TIdCustomHTTPServer = class(TIdCustomTCPServer)
  361. protected
  362. FAutoStartSession: Boolean;
  363. FKeepAlive: Boolean;
  364. FKeepAliveMaxRequests: Integer;
  365. FKeepAliveTimeout: Integer;
  366. FParseParams: Boolean;
  367. FServerSoftware: string;
  368. FMIMETable: TIdThreadSafeMimeTable;
  369. {$IF DEFINED(HAS_UNSAFE_OBJECT_REF)}[Unsafe]
  370. {$ELSEIF DEFINED(HAS_WEAK_OBJECT_REF)}[Weak]
  371. {$IFEND} FSessionList: TIdHTTPCustomSessionList;
  372. FSessionState: Boolean;
  373. FSessionTimeOut: Integer;
  374. //
  375. FOnCreatePostStream: TIdHTTPCreatePostStream;
  376. FOnDoneWithPostStream: TIdHTTPDoneWithPostStream;
  377. FOnCreateSession: TIdHTTPCreateSession;
  378. FOnInvalidSession: TIdHTTPInvalidSessionEvent;
  379. FOnParseAuthentication: TIdHTTPParseAuthenticationEvent;
  380. FOnSessionEnd: TIdHTTPSessionEndEvent;
  381. FOnSessionStart: TIdHTTPSessionStartEvent;
  382. FOnCommandGet: TIdHTTPCommandEvent;
  383. FOnCommandOther: TIdHTTPCommandEvent;
  384. FOnCommandError: TIdHTTPCommandError;
  385. FOnHeadersAvailable: TIdHTTPHeadersAvailableEvent;
  386. FOnHeadersBlocked: TIdHTTPHeadersBlockedEvent;
  387. FOnHeaderExpectations: TIdHTTPHeaderExpectationsEvent;
  388. FOnQuerySSLPort: TIdHTTPQuerySSLPortEvent;
  389. //
  390. FSessionCleanupThread: TIdThread;
  391. FMaximumHeaderLineCount: Integer;
  392. FSessionIDCookieName: string;
  393. //
  394. procedure CreatePostStream(ASender: TIdContext; AHeaders: TIdHeaderList; var VPostStream: TStream); virtual;
  395. procedure DoneWithPostStream(ASender: TIdContext; ARequestInfo: TIdHTTPRequestInfo); virtual;
  396. procedure DoOnCreateSession(AContext: TIdContext; var VNewSession: TIdHTTPSession); virtual;
  397. procedure DoInvalidSession(AContext: TIdContext;
  398. ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo;
  399. var VContinueProcessing: Boolean; const AInvalidSessionID: String); virtual;
  400. procedure DoCommandGet(AContext: TIdContext; ARequestInfo: TIdHTTPRequestInfo;
  401. AResponseInfo: TIdHTTPResponseInfo); virtual;
  402. procedure DoCommandOther(AContext: TIdContext; ARequestInfo: TIdHTTPRequestInfo;
  403. AResponseInfo: TIdHTTPResponseInfo); virtual;
  404. procedure DoCommandError(AContext: TIdContext; ARequestInfo: TIdHTTPRequestInfo;
  405. AResponseInfo: TIdHTTPResponseInfo; AException: Exception); virtual;
  406. procedure DoConnect(AContext: TIdContext); override;
  407. function DoHeadersAvailable(ASender: TIdContext; const AUri: String; AHeaders: TIdHeaderList): Boolean; virtual;
  408. procedure DoHeadersBlocked(ASender: TIdContext; AHeaders: TIdHeaderList; var VResponseNo: Integer; var VResponseText, VContentText: String); virtual;
  409. function DoHeaderExpectations(ASender: TIdContext; const AExpectations: String): Boolean; virtual;
  410. function DoParseAuthentication(ASender: TIdContext; const AAuthType, AAuthData: String; var VUsername, VPassword: String): Boolean; virtual;
  411. function DoQuerySSLPort(APort: TIdPort): Boolean; virtual;
  412. procedure DoSessionEnd(Sender: TIdHTTPSession); virtual;
  413. procedure DoSessionStart(Sender: TIdHTTPSession); virtual;
  414. //
  415. function DoExecute(AContext:TIdContext): Boolean; override;
  416. //
  417. procedure Startup; override;
  418. procedure Shutdown; override;
  419. procedure SetSessionList(const AValue: TIdHTTPCustomSessionList);
  420. procedure SetSessionState(const Value: Boolean);
  421. procedure SetSessionIDCookieName(const AValue: string);
  422. function IsSessionIDCookieNameStored: Boolean;
  423. function GetSessionFromCookie(AContext:TIdContext;
  424. AHTTPrequest: TIdHTTPRequestInfo; AHTTPResponse: TIdHTTPResponseInfo;
  425. var VContinueProcessing: Boolean): TIdHTTPSession;
  426. {$IFDEF USE_OBJECT_REF_FREENOTIF}
  427. procedure Notification(AComponent: TComponent; Operation: TOperation); override;
  428. {$ENDIF}
  429. { to be published in TIdHTTPServer}
  430. property OnCreatePostStream: TIdHTTPCreatePostStream read FOnCreatePostStream write FOnCreatePostStream;
  431. property OnDoneWithPostStream: TIdHTTPDoneWithPostStream read FOnDoneWithPostStream write FOnDoneWithPostStream;
  432. property OnCommandGet: TIdHTTPCommandEvent read FOnCommandGet write FOnCommandGet;
  433. public
  434. constructor Create(AOwner: TComponent); override;
  435. function CreateSession(AContext:TIdContext;
  436. HTTPResponse: TIdHTTPResponseInfo;
  437. HTTPRequest: TIdHTTPRequestInfo): TIdHTTPSession;
  438. destructor Destroy; override;
  439. function EndSession(const SessionName: String; const RemoteIP: String = ''): Boolean;
  440. //
  441. property MIMETable: TIdThreadSafeMimeTable read FMIMETable;
  442. property SessionList: TIdHTTPCustomSessionList read FSessionList write SetSessionList;
  443. published
  444. property AutoStartSession: boolean read FAutoStartSession write FAutoStartSession default Id_TId_HTTPAutoStartSession;
  445. property DefaultPort default IdPORT_HTTP;
  446. property KeepAlive: Boolean read FKeepAlive write FKeepAlive default Id_TId_HTTPServer_KeepAlive;
  447. property KeepAliveMaxRequests: Integer read FKeepAliveMaxRequests write FKeepAliveMaxRequests default Id_TId_HTTPServer_KeepAliveMaxRequests;
  448. property KeepAliveTimeout: Integer read FKeepAliveTimeout write FKeepAliveTimeout default Id_TId_HTTPServer_KeepAliveTimeout;
  449. property MaximumHeaderLineCount: Integer read FMaximumHeaderLineCount write FMaximumHeaderLineCount default Id_TId_HTTPMaximumHeaderLineCount;
  450. property ParseParams: boolean read FParseParams write FParseParams default Id_TId_HTTPServer_ParseParams;
  451. property ServerSoftware: string read FServerSoftware write FServerSoftware;
  452. property SessionState: Boolean read FSessionState write SetSessionState default Id_TId_HTTPServer_SessionState;
  453. property SessionTimeOut: Integer read FSessionTimeOut write FSessionTimeOut default Id_TId_HTTPSessionTimeOut;
  454. property SessionIDCookieName: string read FSessionIDCookieName write SetSessionIDCookieName stored IsSessionIDCookieNameStored;
  455. //
  456. property OnCommandError: TIdHTTPCommandError read FOnCommandError write FOnCommandError;
  457. property OnCommandOther: TIdHTTPCommandEvent read FOnCommandOther write FOnCommandOther;
  458. property OnCreateSession: TIdHTTPCreateSession read FOnCreateSession write FOnCreateSession;
  459. property OnInvalidSession: TIdHTTPInvalidSessionEvent read FOnInvalidSession write FOnInvalidSession;
  460. property OnHeadersAvailable: TIdHTTPHeadersAvailableEvent read FOnHeadersAvailable write FOnHeadersAvailable;
  461. property OnHeadersBlocked: TIdHTTPHeadersBlockedEvent read FOnHeadersBlocked write FOnHeadersBlocked;
  462. property OnHeaderExpectations: TIdHTTPHeaderExpectationsEvent read FOnHeaderExpectations write FOnHeaderExpectations;
  463. property OnParseAuthentication: TIdHTTPParseAuthenticationEvent read FOnParseAuthentication write FOnParseAuthentication;
  464. property OnQuerySSLPort: TIdHTTPQuerySSLPortEvent read FOnQuerySSLPort write FOnQuerySSLPort;
  465. property OnSessionStart: TIdHTTPSessionStartEvent read FOnSessionStart write FOnSessionStart;
  466. property OnSessionEnd: TIdHTTPSessionEndEvent read FOnSessionEnd write FOnSessionEnd;
  467. end;
  468. TIdHTTPDefaultSessionList = Class(TIdHTTPCustomSessionList)
  469. protected
  470. FSessionList: TIdHTTPSessionThreadList;
  471. procedure RemoveSession(Session: TIdHTTPSession); override;
  472. // remove a session surgically when list already locked down (prevent deadlock)
  473. procedure RemoveSessionFromLockedList(AIndex: Integer; ALockedSessionList: TIdHTTPSessionList);
  474. public
  475. constructor Create(AOwner: TComponent); override;
  476. destructor Destroy; override;
  477. property SessionList: TIdHTTPSessionThreadList read FSessionList;
  478. procedure Clear; override;
  479. procedure Add(ASession: TIdHTTPSession); override;
  480. procedure PurgeStaleSessions(PurgeAll: Boolean = false); override;
  481. function CreateUniqueSession(const RemoteIP: String): TIdHTTPSession; override;
  482. function CreateSession(const RemoteIP, SessionID: String): TIdHTTPSession; override;
  483. function GetSession(const SessionID, RemoteIP: string): TIdHTTPSession; override;
  484. end;
  485. TIdHTTPRangeStream = class(TStream)
  486. private
  487. FSourceStream: TStream;
  488. FOwnsSource: Boolean;
  489. FRangeStart, FRangeEnd: Int64;
  490. FResponseCode: Integer;
  491. protected
  492. procedure SetSize(const NewSize: Int64); override;
  493. public
  494. constructor Create(ASource: TStream; ARangeStart, ARangeEnd: Int64; AOwnsSource: Boolean = True);
  495. destructor Destroy; override;
  496. function Read(var Buffer; Count: Longint): Longint; override;
  497. function Write(const Buffer; Count: Longint): Longint; override;
  498. function Seek(const Offset: Int64; Origin: TSeekOrigin): Int64; override;
  499. property ResponseCode: Integer read FResponseCode;
  500. property RangeStart: Int64 read FRangeStart;
  501. property RangeEnd: Int64 read FRangeEnd;
  502. property SourceStream: TStream read FSourceStream;
  503. end;
  504. implementation
  505. uses
  506. {$IFDEF DCC_XE3_OR_ABOVE}
  507. System.SyncObjs,
  508. {$ENDIF}
  509. {$IF DEFINED(KYLIXCOMPAT)}
  510. Libc,
  511. {$ELSEIF DEFINED(USE_VCL_POSIX)}
  512. Posix.SysSelect,
  513. Posix.SysTime,
  514. {$ELSEIF DEFINED(DCC_2010_OR_ABOVE) AND DEFINED(WINDOWS)}
  515. Windows,
  516. {$IFEND}
  517. IdCoderMIME, IdResourceStringsProtocols, IdURI, IdIOHandler, IdIOHandlerSocket,
  518. IdSSL, IdResourceStringsCore;
  519. const
  520. SessionCapacity = 128;
  521. ContentTypeFormUrlencoded = 'application/x-www-form-urlencoded'; {Do not Localize}
  522. // Calculate the number of MS between two TimeStamps
  523. function TimeStampInterval(const AStartStamp, AEndStamp: TDateTime): integer;
  524. begin
  525. Result := Trunc((AEndStamp - AStartStamp) * MSecsPerDay);
  526. end;
  527. { //(Bas Gooijen) was:
  528. function TimeStampInterval(StartStamp, EndStamp: TDateTime): integer;
  529. var
  530. days: Integer;
  531. hour, min, s, ms: Word;
  532. begin
  533. days := Trunc(EndStamp - StartStamp); // whole days
  534. DecodeTime(EndStamp - StartStamp, hour, min, s, ms);
  535. Result := (((days * 24 + hour) * 60 + min) * 60 + s) * 1000 + ms;
  536. end;
  537. }
  538. function GetRandomString(NumChar: UInt32): string;
  539. const
  540. CharMap = 'qwertzuiopasdfghjklyxcvbnmQWERTZUIOPASDFGHJKLYXCVBNM1234567890'; {Do not Localize}
  541. MaxChar: UInt32 = Length(CharMap) - 1;
  542. var
  543. i: integer;
  544. {$IFDEF STRING_IS_IMMUTABLE}
  545. LSB: TIdStringBuilder;
  546. {$ENDIF}
  547. begin
  548. randomize;
  549. {$IFDEF STRING_IS_IMMUTABLE}
  550. LSB := TIdStringBuilder.Create(NumChar);
  551. {$ELSE}
  552. SetLength(Result, NumChar);
  553. {$ENDIF}
  554. for i := 1 to NumChar do
  555. begin
  556. // Add one because CharMap is 1-based
  557. {$IFDEF STRING_IS_IMMUTABLE}
  558. LSB.Append(CharMap[Random(MaxChar) + 1]);
  559. {$ELSE}
  560. Result[i] := CharMap[Random(MaxChar) + 1];
  561. {$ENDIF}
  562. end;
  563. {$IFDEF STRING_IS_IMMUTABLE}
  564. Result := LSB.ToString;
  565. {$ENDIF}
  566. end;
  567. function DecodeHTTPCommand(const ACmd: string): THTTPCommandType;
  568. var
  569. I: Integer;
  570. begin
  571. Result := hcUnknown;
  572. for I := Low(HTTPRequestStrings) to High(HTTPRequestStrings) do begin
  573. if TextIsSame(ACmd, HTTPRequestStrings[i]) then begin
  574. Result := THTTPCommandType(i);
  575. Exit;
  576. end;
  577. end; // for
  578. end;
  579. type
  580. TIdHTTPSessionCleanerThread = Class(TIdThread)
  581. protected
  582. // TODO: use [Unsafe] instead?
  583. {$IFDEF HAS_WEAK_OBJECT_REF}[Weak]{$ENDIF} FSessionList: TIdHTTPCustomSessionList;
  584. public
  585. constructor Create(SessionList: TIdHTTPCustomSessionList); reintroduce;
  586. procedure AfterRun; override;
  587. procedure Run; override;
  588. end; // class
  589. function InternalReadLn(AIOHandler: TIdIOHandler): String;
  590. begin
  591. Result := AIOHandler.ReadLn;
  592. if AIOHandler.ReadLnTimedout then begin
  593. raise EIdReadTimeout.Create(RSReadTimeout);
  594. end;
  595. end;
  596. { TIdThreadSafeMimeTable }
  597. constructor TIdThreadSafeMimeTable.Create(const AutoFill: Boolean = True);
  598. begin
  599. inherited Create;
  600. FTable := TIdMimeTable.Create(AutoFill);
  601. end;
  602. destructor TIdThreadSafeMimeTable.Destroy;
  603. begin
  604. inherited Lock;
  605. try
  606. FreeAndNil(FTable);
  607. finally
  608. inherited Unlock;
  609. end;
  610. inherited Destroy;
  611. end;
  612. function TIdThreadSafeMimeTable.GetLoadTypesFromOS: Boolean;
  613. begin
  614. Lock;
  615. try
  616. Result := FTable.LoadTypesFromOS;
  617. finally
  618. Unlock;
  619. end;
  620. end;
  621. procedure TIdThreadSafeMimeTable.SetLoadTypesFromOS(AValue: Boolean);
  622. begin
  623. Lock;
  624. try
  625. FTable.LoadTypesFromOS := AValue;
  626. finally
  627. Unlock;
  628. end;
  629. end;
  630. function TIdThreadSafeMimeTable.GetOnBuildCache: TNotifyEvent;
  631. begin
  632. Lock;
  633. try
  634. Result := FTable.OnBuildCache;
  635. finally
  636. Unlock;
  637. end;
  638. end;
  639. procedure TIdThreadSafeMimeTable.SetOnBuildCache(AValue: TNotifyEvent);
  640. begin
  641. Lock;
  642. try
  643. FTable.OnBuildCache := AValue;
  644. finally
  645. Unlock;
  646. end;
  647. end;
  648. procedure TIdThreadSafeMimeTable.BuildCache;
  649. begin
  650. Lock;
  651. try
  652. FTable.BuildCache;
  653. finally
  654. Unlock;
  655. end;
  656. end;
  657. procedure TIdThreadSafeMimeTable.AddMimeType(const Ext, MIMEType: string; const ARaiseOnError: Boolean = True);
  658. begin
  659. Lock;
  660. try
  661. FTable.AddMimeType(Ext, MIMEType, ARaiseOnError);
  662. finally
  663. Unlock;
  664. end;
  665. end;
  666. function TIdThreadSafeMimeTable.GetFileMIMEType(const AFileName: string): string;
  667. begin
  668. Lock;
  669. try
  670. Result := FTable.GetFileMIMEType(AFileName);
  671. finally
  672. Unlock;
  673. end;
  674. end;
  675. function TIdThreadSafeMimeTable.GetDefaultFileExt(const MIMEType: string): string;
  676. begin
  677. Lock;
  678. try
  679. Result := FTable.GetDefaultFileExt(MIMEType);
  680. finally
  681. Unlock;
  682. end;
  683. end;
  684. procedure TIdThreadSafeMimeTable.LoadFromStrings(const AStrings: TStrings; const MimeSeparator: Char = '='); {Do not Localize}
  685. begin
  686. Lock;
  687. try
  688. FTable.LoadFromStrings(AStrings, MimeSeparator);
  689. finally
  690. Unlock;
  691. end;
  692. end;
  693. procedure TIdThreadSafeMimeTable.SaveToStrings(const AStrings: TStrings; const MimeSeparator: Char = '='); {Do not Localize}
  694. begin
  695. Lock;
  696. try
  697. FTable.SaveToStrings(AStrings, MimeSeparator);
  698. finally
  699. Unlock;
  700. end;
  701. end;
  702. function TIdThreadSafeMimeTable.Lock: TIdMimeTable;
  703. begin
  704. inherited Lock;
  705. Result := FTable;
  706. end;
  707. procedure TIdThreadSafeMimeTable.Unlock;
  708. begin
  709. inherited Unlock;
  710. end;
  711. { TIdHTTPRangeStream }
  712. constructor TIdHTTPRangeStream.Create(ASource: TStream; ARangeStart, ARangeEnd: Int64;
  713. AOwnsSource: Boolean = True);
  714. var
  715. LSize: Int64;
  716. begin
  717. inherited Create;
  718. FSourceStream := ASource;
  719. FOwnsSource := AOwnsSource;
  720. FResponseCode := 200;
  721. if (ARangeStart > -1) or (ARangeEnd > -1) then begin
  722. LSize := ASource.Size;
  723. if ARangeStart > -1 then begin
  724. // requesting prefix range from BOF
  725. if ARangeStart >= LSize then begin
  726. // range unsatisfiable
  727. FResponseCode := 416;
  728. Exit;
  729. end;
  730. if ARangeEnd > -1 then begin
  731. if ARangeEnd < ARangeStart then begin
  732. // invalid syntax
  733. Exit;
  734. end;
  735. ARangeEnd := IndyMin(ARangeEnd, LSize-1);
  736. end else begin
  737. ARangeEnd := LSize-1;
  738. end;
  739. end else begin
  740. // requesting suffix range from EOF
  741. if ARangeEnd = 0 then begin
  742. // range unsatisfiable
  743. FResponseCode := 416;
  744. Exit;
  745. end;
  746. ARangeStart := IndyMax(LSize - ARangeEnd, 0);
  747. ARangeEnd := LSize-1;
  748. end;
  749. FResponseCode := 206;
  750. FRangeStart := ARangeStart;
  751. FRangeEnd := ARangeEnd;
  752. end;
  753. end;
  754. destructor TIdHTTPRangeStream.Destroy;
  755. begin
  756. if FOwnsSource then begin
  757. IdDisposeAndNil(FSourceStream);
  758. end;
  759. inherited Destroy;
  760. end;
  761. function TIdHTTPRangeStream.Read(var Buffer; Count: Longint): Longint;
  762. begin
  763. if FResponseCode = 206 then begin
  764. Count := Longint(IndyMin(Int64(Count), (FRangeEnd+1) - FSourceStream.Position));
  765. end;
  766. Result := FSourceStream.Read(Buffer, Count);
  767. end;
  768. function TIdHTTPRangeStream.Seek(const Offset: Int64; Origin: TSeekOrigin): Int64;
  769. var
  770. LOffset: Int64;
  771. begin
  772. if FResponseCode = 206 then begin
  773. case Origin of
  774. soBeginning: LOffset := FRangeStart + Offset;
  775. soCurrent: LOffset := FSourceStream.Position + Offset;
  776. soEnd: LOffset := (FRangeEnd+1) + Offset;
  777. else
  778. // TODO: move this into IdResourceStringsProtocols.pas
  779. raise EIdException.Create('Unknown Seek Origin'); {do not localize} // TODO: add a resource string, and create a new Exception class for this
  780. end;
  781. LOffset := IndyMax(LOffset, FRangeStart);
  782. LOffset := IndyMin(LOffset, FRangeEnd+1);
  783. Result := FSourceStream.Seek(LOffset, soBeginning) - FRangeStart;
  784. end else begin
  785. Result := FSourceStream.Seek(Offset, Origin);
  786. end;
  787. end;
  788. function TIdHTTPRangeStream.Write(const Buffer; Count: Longint): Longint;
  789. begin
  790. Result := 0;
  791. end;
  792. procedure TIdHTTPRangeStream.SetSize(const NewSize: Int64);
  793. begin
  794. end;
  795. { TIdCustomHTTPServer }
  796. constructor TIdCustomHTTPServer.Create(AOwner: TComponent);
  797. begin
  798. inherited Create(AOwner);
  799. FSessionState := Id_TId_HTTPServer_SessionState;
  800. DefaultPort := IdPORT_HTTP;
  801. ParseParams := Id_TId_HTTPServer_ParseParams;
  802. FMIMETable := TIdThreadSafeMimeTable.Create(False);
  803. FSessionTimeOut := Id_TId_HTTPSessionTimeOut;
  804. FAutoStartSession := Id_TId_HTTPAutoStartSession;
  805. FKeepAlive := Id_TId_HTTPServer_KeepAlive;
  806. FKeepAliveMaxRequests := Id_TId_HTTPServer_KeepAliveMaxRequests;
  807. FKeepAliveTimeout := Id_TId_HTTPServer_KeepAliveTimeout;
  808. FMaximumHeaderLineCount := Id_TId_HTTPMaximumHeaderLineCount;
  809. FSessionIDCookieName := GSessionIDCookie;
  810. end;
  811. destructor TIdCustomHTTPServer.Destroy;
  812. begin
  813. Active := False; // Set Active to false in order to close all active sessions.
  814. FMIMETable.Free;
  815. inherited Destroy;
  816. end;
  817. // under ARC, all weak references to a freed object get nil'ed automatically
  818. {$IFDEF USE_OBJECT_REF_FREENOTIF}
  819. procedure TIdCustomHTTPServer.Notification(AComponent: TComponent; Operation: TOperation);
  820. begin
  821. if (Operation = opRemove) and (AComponent = FSessionList) then begin
  822. FSessionList := nil;
  823. end;
  824. inherited Notification(AComponent, Operation);
  825. end;
  826. {$ENDIF}
  827. function TIdCustomHTTPServer.DoParseAuthentication(ASender: TIdContext;
  828. const AAuthType, AAuthData: String; var VUsername, VPassword: String): Boolean;
  829. var
  830. s: String;
  831. LDecoder: TIdDecoderMIME;
  832. begin
  833. Result := False;
  834. if Assigned(FOnParseAuthentication) then begin
  835. FOnParseAuthentication(ASender, AAuthType, AAuthData, VUsername, VPassword, Result);
  836. end;
  837. if (not Result) and TextIsSame(AAuthType, 'Basic') then begin {Do not Localize}
  838. LDecoder := TIdDecoderMIME.Create;
  839. try
  840. s := LDecoder.DecodeString(AAuthData, IndyTextEncoding_UTF8);
  841. finally
  842. LDecoder.Free;
  843. end;
  844. VUsername := Fetch(s, ':'); {Do not Localize}
  845. VPassword := s;
  846. Result := True;
  847. end;
  848. end;
  849. procedure TIdCustomHTTPServer.DoOnCreateSession(AContext: TIdContext; Var VNewSession: TIdHTTPSession);
  850. begin
  851. VNewSession := nil;
  852. if Assigned(FOnCreateSession) then
  853. begin
  854. OnCreateSession(AContext, VNewSession);
  855. end;
  856. end;
  857. function TIdCustomHTTPServer.CreateSession(AContext: TIdContext; HTTPResponse: TIdHTTPResponseInfo;
  858. HTTPRequest: TIdHTTPRequestInfo): TIdHTTPSession;
  859. var
  860. LCookie: TIdCookie;
  861. // under ARC, convert a weak reference to a strong reference before working with it
  862. LSessionList: TIdHTTPCustomSessionList;
  863. begin
  864. Result := nil;
  865. if SessionState then begin
  866. LSessionList := FSessionList;
  867. if Assigned(LSessionList) then begin
  868. // TODO: pass the RemoteIP to the OnCreateSession event handler, or even
  869. // better the entire HTTPRequest object...
  870. DoOnCreateSession(AContext, Result);
  871. if not Assigned(Result) then begin
  872. Result := LSessionList.CreateUniqueSession(HTTPRequest.RemoteIP);
  873. end else begin
  874. LSessionList.Add(Result);
  875. end;
  876. LCookie := HTTPResponse.Cookies.Add;
  877. LCookie.CookieName := SessionIDCookieName;
  878. LCookie.Value := Result.SessionID;
  879. LCookie.Path := '/'; {Do not Localize}
  880. // By default the cookie will be valid until the user has closed his browser window.
  881. // MaxAge := SessionTimeOut div 1000;
  882. HTTPResponse.FSession := Result;
  883. HTTPRequest.FSession := Result;
  884. end;
  885. end;
  886. end;
  887. procedure TIdCustomHTTPServer.DoCommandGet(AContext: TIdContext;
  888. ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
  889. begin
  890. if Assigned(FOnCommandGet) then begin
  891. FOnCommandGet(AContext, ARequestInfo, AResponseInfo);
  892. end;
  893. end;
  894. procedure TIdCustomHTTPServer.DoCommandOther(AContext: TIdContext;
  895. ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
  896. begin
  897. if Assigned(FOnCommandOther) then begin
  898. FOnCommandOther(AContext, ARequestInfo, AResponseInfo);
  899. end;
  900. end;
  901. procedure TIdCustomHTTPServer.DoCommandError(AContext: TIdContext;
  902. ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo;
  903. AException: Exception);
  904. begin
  905. if Assigned(FOnCommandError) then begin
  906. FOnCommandError(AContext, ARequestInfo, AResponseInfo, AException);
  907. end;
  908. end;
  909. procedure TIdCustomHTTPServer.DoConnect(AContext: TIdContext);
  910. begin
  911. // RLebeau 6/17/08: let the user decide whether to enable SSL in their
  912. // own event handler. Indy should not be making any assumptions about
  913. // whether to implicitally force SSL on any given connection. This
  914. // prevents a single server from handling both SSL and non-SSL connections
  915. // together. The whole point of the PassThrough property is to allow
  916. // per-connection SSL handling.
  917. //
  918. // TODO: move this new logic into TIdCustomTCPServer directly somehow
  919. if AContext.Connection.IOHandler is TIdSSLIOHandlerSocketBase then begin
  920. TIdSSLIOHandlerSocketBase(AContext.Connection.IOHandler).PassThrough :=
  921. not DoQuerySSLPort(AContext.Connection.Socket.Binding.Port);
  922. end;
  923. inherited DoConnect(AContext);
  924. end;
  925. function TIdCustomHTTPServer.DoQuerySSLPort(APort: TIdPort): Boolean;
  926. begin
  927. // check for the default HTTPS port, but let the user override that if desired...
  928. Result := (APort = IdPORT_https);
  929. if Assigned(FOnQuerySSLPort) then begin
  930. FOnQuerySSLPort(APort, Result);
  931. end;
  932. end;
  933. function TIdCustomHTTPServer.DoHeadersAvailable(ASender: TIdContext; const AUri: String;
  934. AHeaders: TIdHeaderList): Boolean;
  935. begin
  936. Result := True;
  937. if Assigned(OnHeadersAvailable) then begin
  938. OnHeadersAvailable(ASender, AUri, AHeaders, Result);
  939. end;
  940. end;
  941. procedure TIdCustomHTTPServer.DoHeadersBlocked(ASender: TIdContext; AHeaders: TIdHeaderList;
  942. var VResponseNo: Integer; var VResponseText, VContentText: String);
  943. begin
  944. VResponseNo := 403;
  945. VResponseText := '';
  946. VContentText := '';
  947. if Assigned(OnHeadersBlocked) then begin
  948. OnHeadersBlocked(ASender, AHeaders, VResponseNo, VResponseText, VContentText);
  949. end;
  950. end;
  951. function TIdCustomHTTPServer.DoHeaderExpectations(ASender: TIdContext; const AExpectations: String): Boolean;
  952. begin
  953. Result := TextIsSame(AExpectations, '100-continue'); {Do not Localize}
  954. if Assigned(OnHeaderExpectations) then begin
  955. OnHeaderExpectations(ASender, AExpectations, Result);
  956. end;
  957. end;
  958. function TIdCustomHTTPServer.DoExecute(AContext:TIdContext): boolean;
  959. var
  960. LRequestInfo: TIdHTTPRequestInfo;
  961. LResponseInfo: TIdHTTPResponseInfo;
  962. procedure ReadCookiesFromRequestHeader;
  963. var
  964. LRawCookies: TStringList;
  965. begin
  966. LRawCookies := TStringList.Create;
  967. try
  968. LRequestInfo.RawHeaders.Extract('Cookie', LRawCookies); {Do not Localize}
  969. LRequestInfo.Cookies.AddClientCookies(LRawCookies);
  970. finally
  971. LRawCookies.Free;
  972. end;
  973. end;
  974. function GetRemoteIP(ASocket: TIdIOHandlerSocket): String;
  975. begin
  976. Result := '';
  977. if ASocket <> nil then begin
  978. if ASocket.Binding <> nil then begin
  979. Result := ASocket.Binding.PeerIP;
  980. end;
  981. end;
  982. end;
  983. function HeadersCanContinue: Boolean;
  984. var
  985. LResponseNo: Integer;
  986. LResponseText, LContentText, S: String;
  987. begin
  988. // let the user decide if the request headers are acceptable
  989. // TODO pass the whole LRequestInfo object so the user has access
  990. // to the request method, too...
  991. Result := DoHeadersAvailable(AContext, LRequestInfo.URI, LRequestInfo.RawHeaders);
  992. if not Result then begin
  993. DoHeadersBlocked(AContext, LRequestInfo.RawHeaders, LResponseNo, LResponseText, LContentText);
  994. LResponseInfo.ResponseNo := LResponseNo;
  995. if LResponseText <> '' then begin
  996. LResponseInfo.ResponseText := LResponseText;
  997. end;
  998. LResponseInfo.ContentText := LContentText;
  999. LResponseInfo.CharSet := 'utf-8'; {Do not localize}
  1000. LResponseInfo.CloseConnection := True;
  1001. LResponseInfo.WriteHeader;
  1002. if LContentText <> '' then begin
  1003. LResponseInfo.WriteContent;
  1004. end;
  1005. Exit;
  1006. end;
  1007. // check for HTTP v1.1 'Host' and 'Expect' headers...
  1008. if not LRequestInfo.IsVersionAtLeast(1, 1) then begin
  1009. Exit;
  1010. end;
  1011. // MUST report a 400 (Bad Request) error if an HTTP/1.1
  1012. // request does not include a 'Host' header
  1013. S := LRequestInfo.RawHeaders.Values['Host'];
  1014. if S = '' then begin
  1015. LResponseInfo.ResponseNo := 400;
  1016. LResponseInfo.CloseConnection := True;
  1017. LResponseInfo.WriteHeader;
  1018. Exit;
  1019. end;
  1020. // if the client has already sent some or all of the request
  1021. // body then don't bother checking for a v1.1 'Expect' header
  1022. // TODO: call IOHandler.CheckForDataOnSource(0)...
  1023. if not AContext.Connection.IOHandler.InputBufferIsEmpty then begin
  1024. Exit;
  1025. end;
  1026. S := LRequestInfo.RawHeaders.Values['Expect'];
  1027. if S = '' then begin
  1028. Exit;
  1029. end;
  1030. // check if the client expectations can be satisfied...
  1031. Result := DoHeaderExpectations(AContext, S);
  1032. if not Result then begin
  1033. LResponseInfo.ResponseNo := 417;
  1034. LResponseInfo.CloseConnection := True;
  1035. LResponseInfo.WriteHeader;
  1036. Exit;
  1037. end;
  1038. if Pos('100-continue', LowerCase(S)) > 0 then begin {Do not Localize}
  1039. // the client requested a '100-continue' expectation so send
  1040. // a '100 Continue' reply now before the request body can be read
  1041. AContext.Connection.IOHandler.WriteLn(LRequestInfo.Version + ' 100 ' + RSHTTPContinue + EOL); {Do not Localize}
  1042. end;
  1043. end;
  1044. function PreparePostStream: Boolean;
  1045. var
  1046. I, Size: Integer;
  1047. S: String;
  1048. LIOHandler: TIdIOHandler;
  1049. begin
  1050. Result := False;
  1051. LIOHandler := AContext.Connection.IOHandler;
  1052. // RLebeau 1/6/2009: don't create the PostStream unless there is
  1053. // actually something to read. This should make it easier for the
  1054. // request handler to know when to use the PostStream and when to
  1055. // use the (Unparsed)Params instead...
  1056. if (LRequestInfo.TransferEncoding <> '') and
  1057. (not TextIsSame(LRequestInfo.TransferEncoding, 'identity')) then {do not localize}
  1058. begin
  1059. if IndyPos('chunked', LowerCase(LRequestInfo.TransferEncoding)) = 0 then begin {do not localize}
  1060. LResponseInfo.ResponseNo := 400; // bad request
  1061. LResponseInfo.CloseConnection := True;
  1062. LResponseInfo.WriteHeader;
  1063. Exit;
  1064. end;
  1065. // there is no good reason for GET/HEAD requests to ever have a non-empty
  1066. // message body, but some REST APIs do allow DELETE requests to carry a
  1067. // body to identify the resource(s) to be deleted...
  1068. // TODO: check if the 1st chunk is 0 bytes...
  1069. if LRequestInfo.CommandType in [hcGET, hcHEAD{, hcDELETE}] then
  1070. begin
  1071. LResponseInfo.ResponseNo := 400; // bad request
  1072. LResponseInfo.CloseConnection := True;
  1073. LResponseInfo.WriteHeader;
  1074. Exit;
  1075. end;
  1076. CreatePostStream(AContext, LRequestInfo.RawHeaders, LRequestInfo.FPostStream);
  1077. if LRequestInfo.FPostStream = nil then begin
  1078. LRequestInfo.FPostStream := TMemoryStream.Create;
  1079. end;
  1080. // TODO: do not seek here. Leave the Position where CreatePostStream()
  1081. // left it, in case the user decides to use a custom stream that does
  1082. // not start at Position 0.
  1083. LRequestInfo.PostStream.Position := 0;
  1084. repeat
  1085. S := InternalReadLn(LIOHandler);
  1086. I := IndyPos(';', S); {do not localize}
  1087. if I > 0 then begin
  1088. S := Copy(S, 1, I - 1);
  1089. end;
  1090. Size := IndyStrToInt('$' + Trim(S), 0); {do not localize}
  1091. if Size = 0 then begin
  1092. Break;
  1093. end;
  1094. LIOHandler.ReadStream(LRequestInfo.PostStream, Size);
  1095. InternalReadLn(LIOHandler); // CRLF at end of chunk data
  1096. until False;
  1097. // skip trailer headers
  1098. repeat until InternalReadLn(LIOHandler) = '';
  1099. // TODO: seek back to the original Position where CreatePostStream()
  1100. // left it, not all the way back to Position 0.
  1101. LRequestInfo.PostStream.Position := 0;
  1102. end
  1103. else if LRequestInfo.HasContentLength then
  1104. begin
  1105. // there is no good reason for GET/HEAD requests to ever have a non-empty
  1106. // message body, but some REST APIs do allow DELETE requests to carry a
  1107. // body to identify the resource(s) to be deleted...
  1108. if (LRequestInfo.CommandType in [hcGET, hcHEAD{, hcDELETE}]) and (LRequestInfo.ContentLength <> 0) then
  1109. begin
  1110. LResponseInfo.ResponseNo := 400; // bad request
  1111. LResponseInfo.CloseConnection := True;
  1112. LResponseInfo.WriteHeader;
  1113. Exit;
  1114. end;
  1115. CreatePostStream(AContext, LRequestInfo.RawHeaders, LRequestInfo.FPostStream);
  1116. if LRequestInfo.FPostStream = nil then begin
  1117. LRequestInfo.FPostStream := TMemoryStream.Create;
  1118. end;
  1119. // TODO: do not seek here. Leave the Position where CreatePostStream()
  1120. // left it, in case the user decides to use a custom stream that does
  1121. // not start at Position 0.
  1122. LRequestInfo.PostStream.Position := 0;
  1123. if LRequestInfo.ContentLength > 0 then begin
  1124. LIOHandler.ReadStream(LRequestInfo.PostStream, LRequestInfo.ContentLength);
  1125. // TODO: seek back to the original Position where CreatePostStream()
  1126. // left it, not all the way back to Position 0.
  1127. LRequestInfo.PostStream.Position := 0;
  1128. end;
  1129. end
  1130. // If HTTP Pipelining is used by the client, bytes may exist that belong to
  1131. // the NEXT request! We need to look at the CURRENT request and only check
  1132. // for misreported body data if a body is actually expected. GET and HEAD
  1133. // requests do not have bodies...
  1134. else if LRequestInfo.CommandType in [hcPOST, hcPUT] then
  1135. begin
  1136. // TODO: need to handle the case where the ContentType is 'multipart/...',
  1137. // which is self-terminating and does not strictly require the above headers...
  1138. if LIOHandler.InputBufferIsEmpty then begin
  1139. LIOHandler.CheckForDataOnSource(1);
  1140. end;
  1141. if not LIOHandler.InputBufferIsEmpty then begin
  1142. LResponseInfo.ResponseNo := 411; // length required
  1143. LResponseInfo.CloseConnection := True;
  1144. LResponseInfo.WriteHeader;
  1145. Exit;
  1146. end;
  1147. end;
  1148. Result := True;
  1149. end;
  1150. var
  1151. i: integer;
  1152. s, LInputLine, LRawHTTPCommand, LCmd, LContentType: String;
  1153. LURI: TIdURI;
  1154. LContinueProcessing, LCloseConnection: Boolean;
  1155. LConn: TIdTCPConnection;
  1156. LEncoding: IIdTextEncoding;
  1157. LNumRequestsAllowed: Integer;
  1158. begin
  1159. LContinueProcessing := True;
  1160. Result := False;
  1161. LCloseConnection := not KeepAlive;
  1162. try
  1163. try
  1164. LConn := AContext.Connection;
  1165. if (FKeepAliveTimeout > 0) and
  1166. AContext.Connection.IOHandler.InputBufferIsEmpty then
  1167. begin
  1168. AContext.Connection.IOHandler.CheckForDataOnSource(FKeepAliveTimeout * 1000);
  1169. if AContext.Connection.IOHandler.InputBufferIsEmpty then begin
  1170. Exit;
  1171. end;
  1172. end;
  1173. LNumRequestsAllowed := KeepAliveMaxRequests;
  1174. repeat
  1175. LInputLine := InternalReadLn(LConn.IOHandler);
  1176. i := RPos(' ', LInputLine, -1); {Do not Localize}
  1177. if i = 0 then begin
  1178. raise EIdHTTPErrorParsingCommand.Create(RSHTTPErrorParsingCommand);
  1179. end;
  1180. // TODO: don't recreate the Request and Response objects on each loop
  1181. // iteration. Just create them once before entering the loop, and then
  1182. // reset them as needed on each iteration...
  1183. LRequestInfo := TIdHTTPRequestInfo.Create(Self);
  1184. try
  1185. LRequestInfo.FNumRequestsAllowed := LNumRequestsAllowed;
  1186. LResponseInfo := TIdHTTPResponseInfo.Create(Self, LRequestInfo, LConn);
  1187. try
  1188. // SG 05.07.99
  1189. // Set the ServerSoftware string to what it's supposed to be. {Do not Localize}
  1190. LResponseInfo.ServerSoftware := Trim(ServerSoftware);
  1191. // S.G. 6/4/2004: Set the maximum number of lines that will be catured
  1192. // S.G. 6/4/2004: to prevent a remote resource starvation DOS
  1193. LConn.IOHandler.MaxCapturedLines := MaximumHeaderLineCount;
  1194. // Retrieve the HTTP version
  1195. LRawHTTPCommand := LInputLine;
  1196. LRequestInfo.FVersion := Copy(LInputLine, i + 1, MaxInt);
  1197. s := LRequestInfo.Version;
  1198. Fetch(s, '/'); {Do not localize}
  1199. LRequestInfo.FVersionMajor := IndyStrToInt(Fetch(s, '.'), -1); {Do not Localize}
  1200. LRequestInfo.FVersionMinor := IndyStrToInt(S, -1);
  1201. SetLength(LInputLine, i - 1);
  1202. // Retrieve the HTTP header
  1203. LRequestInfo.RawHeaders.Clear;
  1204. LConn.IOHandler.Capture(LRequestInfo.RawHeaders, '', False); {Do not Localize}
  1205. // TODO: call HeadersCanContinue() here before the headers are parsed,
  1206. // in case the user needs to overwrite any values...
  1207. LRequestInfo.ProcessHeaders;
  1208. // HTTP 1.1 connections are keep-alive by default
  1209. if not FKeepAlive then begin
  1210. LResponseInfo.CloseConnection := True;
  1211. end
  1212. else if LRequestInfo.IsVersionAtLeast(1, 1) then begin
  1213. LResponseInfo.CloseConnection := TextIsSame(LRequestInfo.Connection, 'close'); {Do not Localize}
  1214. end else begin
  1215. LResponseInfo.CloseConnection := not TextIsSame(LRequestInfo.Connection, 'keep-alive'); {Do not Localize}
  1216. end;
  1217. {TODO Check for 1.0 only at this point}
  1218. LCmd := UpperCase(Fetch(LInputLine, ' ')); {Do not Localize}
  1219. // check for overrides when LCmd is 'POST'...
  1220. if TextIsSame(LCmd, 'POST') then begin
  1221. s := LRequestInfo.MethodOverride; // Google/GData
  1222. if s = '' then begin
  1223. // TODO: make RequestInfo properties for these
  1224. s := LRequestInfo.RawHeaders.Values['X-HTTP-Method']; // Microsoft {do not localize}
  1225. if s = '' then begin
  1226. s := LRequestInfo.RawHeaders.Values['X-METHOD-OVERRIDE']; // IBM {do not localize}
  1227. end;
  1228. end;
  1229. if s <> '' then begin
  1230. LCmd := UpperCase(s);
  1231. end;
  1232. end;
  1233. LRequestInfo.FRawHTTPCommand := LRawHTTPCommand;
  1234. LRequestInfo.FRemoteIP := GetRemoteIP(LConn.Socket);
  1235. LRequestInfo.FCommand := LCmd;
  1236. LRequestInfo.FCommandType := DecodeHTTPCommand(LCmd);
  1237. // GET data - may exist with POSTs also
  1238. LRequestInfo.QueryParams := LInputLine;
  1239. LInputLine := Fetch(LRequestInfo.FQueryParams, '?'); {Do not Localize}
  1240. // Host
  1241. // the next line is done in TIdHTTPRequestInfo.ProcessHeaders()...
  1242. // LRequestInfo.FHost := LRequestInfo.Headers.Values['host']; {Do not Localize}
  1243. LRequestInfo.FURI := LInputLine;
  1244. // Parse the document input line
  1245. if LInputLine = '*' then begin {Do not Localize}
  1246. LRequestInfo.FDocument := '*'; {Do not Localize}
  1247. end else begin
  1248. LURI := TIdURI.Create(LInputLine);
  1249. try
  1250. // SG 29/11/01: Per request of Doychin
  1251. // Try to fill the "host" parameter
  1252. LRequestInfo.FDocument := TIdURI.URLDecode(LURI.Path) + TIdURI.URLDecode(LURI.Document);
  1253. if (LURI.Host <> '') and (LRequestInfo.FHost = '') then begin
  1254. LRequestInfo.FHost := LURI.Host;
  1255. end;
  1256. finally
  1257. LURI.Free;
  1258. end;
  1259. end;
  1260. // RLebeau 12/14/2005: provide the user with the headers and let the
  1261. // user decide whether the response processing should continue...
  1262. if not HeadersCanContinue then begin
  1263. Break;
  1264. end;
  1265. // retreive the base ContentType with attributes omitted
  1266. LContentType := ExtractHeaderItem(LRequestInfo.ContentType);
  1267. // Grab Params so we can parse them
  1268. // POSTed data - may exist with GETs also. With GETs, the action
  1269. // params from the form element will be posted
  1270. // TODO: Rune this is the area that needs fixed. Ive hacked it for now
  1271. // Get data can exists with POSTs, but can POST data exist with GETs?
  1272. // If only the first, the solution is easy. If both - need more
  1273. // investigation.
  1274. if not PreparePostStream then begin
  1275. Break;
  1276. end;
  1277. try
  1278. if LRequestInfo.PostStream <> nil then begin
  1279. if TextIsSame(LContentType, ContentTypeFormUrlencoded) then
  1280. begin
  1281. // decoding percent-encoded octets and applying the CharSet is handled by DecodeAndSetParams() further below...
  1282. EnsureEncoding(LEncoding, enc8Bit);
  1283. LRequestInfo.FormParams := ReadStringFromStream(LRequestInfo.PostStream, -1, LEncoding);
  1284. DoneWithPostStream(AContext, LRequestInfo); // don't need the PostStream anymore
  1285. end;
  1286. end;
  1287. // glue together parameters passed in the URL and those
  1288. //
  1289. // RLebeau: should we really be doing this? For a GET, it might
  1290. // makes sense to do, but for a POST the FormParams is the content
  1291. // and the QueryParams belongs to the URL only, not the content.
  1292. // We should be keeping everything separate for accuracy...
  1293. LRequestInfo.UnparsedParams := LRequestInfo.FormParams;
  1294. if LRequestInfo.QueryParams <> '' then begin
  1295. if LRequestInfo.UnparsedParams = '' then begin
  1296. LRequestInfo.FUnparsedParams := LRequestInfo.QueryParams;
  1297. end else begin
  1298. LRequestInfo.FUnparsedParams := LRequestInfo.UnparsedParams + '&' {Do not Localize}
  1299. + LRequestInfo.QueryParams;
  1300. end;
  1301. end;
  1302. // Parse Params
  1303. if ParseParams then begin
  1304. if TextIsSame(LContentType, ContentTypeFormUrlencoded) then begin
  1305. // TODO: decode the data using the algorithm outlined in HTML5 section 4.10.22.6 "URL-encoded form data"
  1306. LRequestInfo.DecodeAndSetParams(LRequestInfo.UnparsedParams);
  1307. end else begin
  1308. // Parse only query params when content type is not 'application/x-www-form-urlencoded' {Do not Localize}
  1309. // TODO: decode the data using a user-specified charset, defaulting to UTF-8
  1310. LRequestInfo.DecodeAndSetParams(LRequestInfo.QueryParams);
  1311. end;
  1312. end;
  1313. // Cookies
  1314. ReadCookiesFromRequestHeader;
  1315. try
  1316. // Authentication
  1317. s := LRequestInfo.RawHeaders.Values['Authorization']; {Do not Localize}
  1318. if s <> '' then begin
  1319. LRequestInfo.FAuthType := Fetch(s, ' ');
  1320. LRequestInfo.FAuthExists := DoParseAuthentication(AContext, LRequestInfo.FAuthType, s, LRequestInfo.FAuthUsername, LRequestInfo.FAuthPassword);
  1321. if not LRequestInfo.FAuthExists then begin
  1322. raise EIdHTTPUnsupportedAuthorisationScheme.Create(RSHTTPUnsupportedAuthorisationScheme);
  1323. end;
  1324. end;
  1325. // Session management
  1326. LContinueProcessing := True;
  1327. GetSessionFromCookie(AContext, LRequestInfo, LResponseInfo, LContinueProcessing);
  1328. if LContinueProcessing then begin
  1329. // These essentially all "retrieve" so they are all "Get"s
  1330. if LRequestInfo.CommandType in [hcGET, hcPOST, hcHEAD] then begin
  1331. DoCommandGet(AContext, LRequestInfo, LResponseInfo);
  1332. end else begin
  1333. DoCommandOther(AContext, LRequestInfo, LResponseInfo);
  1334. end;
  1335. end;
  1336. except
  1337. on E: EIdSocketError do begin // don't stop socket exceptions
  1338. raise;
  1339. end;
  1340. on E: EIdHTTPUnsupportedAuthorisationScheme do begin
  1341. LResponseInfo.ResponseNo := 401;
  1342. LResponseInfo.ContentText := E.Message;
  1343. LResponseInfo.CharSet := 'utf-8'; {Do no localize}
  1344. LContinueProcessing := True;
  1345. for i := 0 to LResponseInfo.WWWAuthenticate.Count - 1 do begin
  1346. S := LResponseInfo.WWWAuthenticate[i];
  1347. if TextIsSame(Fetch(S), 'Basic') then begin {Do not localize}
  1348. LContinueProcessing := False;
  1349. Break;
  1350. end;
  1351. end;
  1352. if LContinueProcessing then begin
  1353. LResponseInfo.WWWAuthenticate.Add('Basic');
  1354. end;
  1355. end;
  1356. on E: Exception do begin
  1357. LResponseInfo.ResponseNo := 500;
  1358. LResponseInfo.ContentText := E.Message;
  1359. LResponseInfo.CharSet := 'utf-8'; {Do not localize}
  1360. DoCommandError(AContext, LRequestInfo, LResponseInfo, E);
  1361. end;
  1362. end;
  1363. // Write even though WriteContent will, may be a redirect or other
  1364. if not LResponseInfo.HeaderHasBeenWritten then begin
  1365. LResponseInfo.WriteHeader;
  1366. end;
  1367. // Always check ContentText first
  1368. if (LResponseInfo.ContentText <> '')
  1369. or Assigned(LResponseInfo.ContentStream) then begin
  1370. LResponseInfo.WriteContent;
  1371. end;
  1372. finally
  1373. if LRequestInfo.PostStream <> nil then begin
  1374. DoneWithPostStream(AContext, LRequestInfo); // don't need the PostStream anymore
  1375. end;
  1376. end;
  1377. finally
  1378. LCloseConnection := LResponseInfo.CloseConnection;
  1379. LResponseInfo.Free;
  1380. end;
  1381. finally
  1382. LRequestInfo.Free;
  1383. end;
  1384. if (not LCloseConnection) and (KeepAliveMaxRequests > 0) then
  1385. begin
  1386. Dec(LNumRequestsAllowed);
  1387. LCloseConnection := (LNumRequestsAllowed < 1);
  1388. end;
  1389. if (not LCloseConnection) and (FKeepAliveTimeout > 0) and
  1390. AContext.Connection.IOHandler.InputBufferIsEmpty then
  1391. begin
  1392. AContext.Connection.IOHandler.CheckForDataOnSource(FKeepAliveTimeout * 1000);
  1393. LCloseConnection := AContext.Connection.IOHandler.InputBufferIsEmpty;
  1394. end;
  1395. until LCloseConnection;
  1396. except
  1397. on E: EIdSocketError do begin
  1398. if not ((E.LastError = Id_WSAESHUTDOWN) or (E.LastError = Id_WSAECONNABORTED) or (E.LastError = Id_WSAECONNRESET)) then begin
  1399. raise;
  1400. end;
  1401. end;
  1402. on E: EIdClosedSocket do begin
  1403. AContext.Connection.Disconnect;
  1404. end;
  1405. end;
  1406. finally
  1407. AContext.Connection.Disconnect(False);
  1408. end;
  1409. end;
  1410. procedure TIdCustomHTTPServer.DoInvalidSession(AContext: TIdContext;
  1411. ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo;
  1412. var VContinueProcessing: Boolean; const AInvalidSessionID: String);
  1413. begin
  1414. if Assigned(FOnInvalidSession) then begin
  1415. FOnInvalidSession(AContext, ARequestInfo, AResponseInfo, VContinueProcessing, AInvalidSessionID)
  1416. end;
  1417. end;
  1418. function TIdCustomHTTPServer.EndSession(const SessionName: String; const RemoteIP: String = ''): Boolean;
  1419. var
  1420. LSession: TIdHTTPSession;
  1421. // under ARC, convert a weak reference to a strong reference before working with it
  1422. LSessionList: TIdHTTPCustomSessionList;
  1423. begin
  1424. Result := False;
  1425. LSessionList := SessionList;
  1426. if Assigned(LSessionList) then begin
  1427. LSession := SessionList.GetSession(SessionName, RemoteIP); {Do not Localize}
  1428. if Assigned(LSession) then begin
  1429. LSessionList.RemoveSession(LSession);
  1430. LSession.DoSessionEnd;
  1431. // must set the owner to nil or the session will try to fire the OnSessionEnd
  1432. // event again, and also remove itself from the session list and deadlock
  1433. LSession.FOwner := nil;
  1434. LSession.Free;
  1435. Result := True;
  1436. end;
  1437. end;
  1438. end;
  1439. procedure TIdCustomHTTPServer.DoSessionEnd(Sender: TIdHTTPSession);
  1440. begin
  1441. if Assigned(FOnSessionEnd) then begin
  1442. FOnSessionEnd(Sender);
  1443. end;
  1444. end;
  1445. procedure TIdCustomHTTPServer.DoSessionStart(Sender: TIdHTTPSession);
  1446. begin
  1447. if Assigned(FOnSessionStart) then begin
  1448. FOnSessionStart(Sender);
  1449. end;
  1450. end;
  1451. function TIdCustomHTTPServer.GetSessionFromCookie(AContext: TIdContext;
  1452. AHTTPRequest: TIdHTTPRequestInfo; AHTTPResponse: TIdHTTPResponseInfo;
  1453. var VContinueProcessing: Boolean): TIdHTTPSession;
  1454. var
  1455. LIndex: Integer;
  1456. LSessionID: String;
  1457. // under ARC, convert a weak reference to a strong reference before working with it
  1458. LSessionList: TIdHTTPCustomSessionList;
  1459. begin
  1460. Result := nil;
  1461. VContinueProcessing := True;
  1462. if SessionState then
  1463. begin
  1464. LSessionList := FSessionList;
  1465. LIndex := AHTTPRequest.Cookies.GetCookieIndex(SessionIDCookieName);
  1466. while LIndex >= 0 do
  1467. begin
  1468. LSessionID := AHTTPRequest.Cookies[LIndex].Value;
  1469. if Assigned(LSessionList) then begin
  1470. Result := LSessionList.GetSession(LSessionID, AHTTPRequest.RemoteIP);
  1471. if Assigned(Result) then begin
  1472. Break;
  1473. end;
  1474. end;
  1475. DoInvalidSession(AContext, AHTTPRequest, AHTTPResponse, VContinueProcessing, LSessionID);
  1476. if not VContinueProcessing then begin
  1477. Break;
  1478. end;
  1479. LIndex := AHTTPRequest.Cookies.GetCookieIndex(SessionIDCookieName, LIndex+1);
  1480. end; { while }
  1481. // check if a session was returned. If not and if AutoStartSession is set to
  1482. // true, Create a new session
  1483. if (Result = nil) and VContinueProcessing and FAutoStartSession then begin
  1484. Result := CreateSession(AContext, AHTTPResponse, AHTTPrequest);
  1485. end;
  1486. end;
  1487. AHTTPRequest.FSession := Result;
  1488. AHTTPResponse.FSession := Result;
  1489. end;
  1490. procedure TIdCustomHTTPServer.Startup;
  1491. var
  1492. // under ARC, convert a weak reference to a strong reference before working with it
  1493. LSessionList: TIdHTTPCustomSessionList;
  1494. begin
  1495. inherited Startup;
  1496. // set the session timeout and options
  1497. LSessionList := FSessionList;
  1498. if not Assigned(LSessionList) then begin
  1499. LSessionList := TIdHTTPDefaultSessionList.Create(Self);
  1500. FSessionList := LSessionList;
  1501. end;
  1502. if FSessionTimeOut <> 0 then begin
  1503. LSessionList.FSessionTimeout := FSessionTimeOut;
  1504. end else begin
  1505. FSessionState := False;
  1506. end;
  1507. // Session events
  1508. LSessionList.OnSessionStart := DoSessionStart;
  1509. LSessionList.OnSessionEnd := DoSessionEnd;
  1510. // If session handling is enabled, create the housekeeper thread
  1511. if SessionState then begin
  1512. FSessionCleanupThread := TIdHTTPSessionCleanerThread.Create(LSessionList);
  1513. end;
  1514. end;
  1515. procedure TIdCustomHTTPServer.Shutdown;
  1516. var
  1517. // under ARC, convert a weak reference to a strong reference before working with it
  1518. LSessionList: TIdHTTPCustomSessionList;
  1519. begin
  1520. // Boost the clear thread priority to give it a good chance to terminate
  1521. if Assigned(FSessionCleanupThread) then begin
  1522. IndySetThreadPriority(FSessionCleanupThread, tpNormal);
  1523. FSessionCleanupThread.TerminateAndWaitFor;
  1524. FreeAndNil(FSessionCleanupThread);
  1525. end;
  1526. // RLebeau: FSessionList might not be assignd yet if Shutdown() is being
  1527. // called due to an exception raised in Startup()...
  1528. LSessionList := FSessionList;
  1529. if Assigned(LSessionList) then begin
  1530. if LSessionList.Owner = Self then begin
  1531. SetSessionList(nil);
  1532. end else begin
  1533. LSessionList.Clear;
  1534. end;
  1535. {$IFDEF USE_OBJECT_ARC}LSessionList := nil;{$ENDIF}
  1536. end;
  1537. inherited Shutdown;
  1538. end;
  1539. procedure TIdCustomHTTPServer.SetSessionList(const AValue: TIdHTTPCustomSessionList);
  1540. var
  1541. // under ARC, convert a weak reference to a strong reference before working with it
  1542. LSessionList: TIdHTTPCustomSessionList;
  1543. begin
  1544. LSessionList := FSessionList;
  1545. if LSessionList <> AValue then
  1546. begin
  1547. // RLebeau - is this really needed? What should happen if this
  1548. // gets called by Notification() if the sessionList is freed while
  1549. // the server is still Active?
  1550. if Active then begin
  1551. raise EIdException.Create(RSHTTPCannotSwitchSessionListWhenActive); // TODO: create a new Exception class for this
  1552. end;
  1553. // under ARC, all weak references to a freed object get nil'ed automatically
  1554. // Free the default implicit SessionList
  1555. if LSessionList.Owner = Self then begin
  1556. // Under D8 notification gets called after .Free of FreeAndNil, but before
  1557. // its set to nil with a side effect of IDisposable. To counteract this we
  1558. // set it to nil first.
  1559. // -Kudzu
  1560. FSessionList := nil;
  1561. IdDisposeAndNil(LSessionList);
  1562. end;
  1563. {$IFDEF USE_OBJECT_REF_FREENOTIF}
  1564. // Ensure we will no longer be notified when the component is freed
  1565. if LSessionList <> nil then begin
  1566. LSessionList.RemoveFreeNotification(Self);
  1567. end;
  1568. {$ENDIF}
  1569. FSessionList := AValue;
  1570. {$IFDEF USE_OBJECT_REF_FREENOTIF}
  1571. // Ensure we will be notified when the component is freed, even is it's on
  1572. // another form
  1573. if AValue <> nil then begin
  1574. AValue.FreeNotification(Self);
  1575. end;
  1576. {$ENDIF}
  1577. end;
  1578. end;
  1579. procedure TIdCustomHTTPServer.SetSessionState(const Value: Boolean);
  1580. begin
  1581. // ToDo: Add thread multiwrite protection here
  1582. if (not (IsDesignTime or IsLoading)) and Active then begin
  1583. raise EIdHTTPCannotSwitchSessionStateWhenActive.Create(RSHTTPCannotSwitchSessionStateWhenActive);
  1584. end;
  1585. FSessionState := Value;
  1586. end;
  1587. procedure TIdCustomHTTPServer.SetSessionIDCookieName(const AValue: string);
  1588. var
  1589. LCookieName: string;
  1590. begin
  1591. // ToDo: Add thread multiwrite protection here
  1592. if (not (IsDesignTime or IsLoading)) and Active then begin
  1593. raise EIdHTTPCannotSwitchSessionIDCookieNameWhenActive.Create(RSHTTPCannotSwitchSessionIDCookieNameWhenActive);
  1594. end;
  1595. LCookieName := Trim(AValue);
  1596. if LCookieName = '' then begin
  1597. // TODO: move this into IdResourceStringsProtocols.pas
  1598. raise EIdException.Create('Invalid cookie name'); {do not localize} // TODO: add a resource string, and create a new Exception class for this
  1599. end;
  1600. FSessionIDCookieName := AValue;
  1601. end;
  1602. function TIdCustomHTTPServer.IsSessionIDCookieNameStored: Boolean;
  1603. begin
  1604. Result := not TextIsSame(SessionIDCookieName, GSessionIDCookie);
  1605. end;
  1606. procedure TIdCustomHTTPServer.CreatePostStream(ASender: TIdContext;
  1607. AHeaders: TIdHeaderList; var VPostStream: TStream);
  1608. begin
  1609. if Assigned(OnCreatePostStream) then begin
  1610. OnCreatePostStream(ASender, AHeaders, VPostStream);
  1611. end;
  1612. end;
  1613. procedure TIdCustomHTTPServer.DoneWithPostStream(ASender: TIdContext;
  1614. ARequestInfo: TIdHTTPRequestInfo);
  1615. var
  1616. LCanFree: Boolean;
  1617. LStream: TStream;
  1618. begin
  1619. LCanFree := True;
  1620. if Assigned(FOnDoneWithPostStream) then begin
  1621. FOnDoneWithPostStream(ASender, ARequestInfo, LCanFree);
  1622. end;
  1623. if LCanFree then begin
  1624. LStream := ARequestInfo.FPostStream;
  1625. ARequestInfo.FPostStream := nil;
  1626. IdDisposeAndNil(LStream);
  1627. end else begin
  1628. ARequestInfo.FPostStream := nil;
  1629. end;
  1630. end;
  1631. { TIdHTTPSession }
  1632. constructor TIdHTTPSession.Create(AOwner: TIdHTTPCustomSessionList);
  1633. begin
  1634. inherited Create;
  1635. FLock := TIdCriticalSection.Create;
  1636. FContent := TStringList.Create;
  1637. FOwner := AOwner;
  1638. if Assigned(AOwner) then
  1639. begin
  1640. if Assigned(AOwner.OnSessionStart) then begin
  1641. AOwner.OnSessionStart(Self);
  1642. end;
  1643. end;
  1644. end;
  1645. constructor TIdHTTPSession.CreateInitialized(AOwner: TIdHTTPCustomSessionList; const SessionID, RemoteIP: string);
  1646. begin
  1647. inherited Create;
  1648. FSessionID := SessionID;
  1649. FRemoteHost := RemoteIP;
  1650. // TODO: use a timer to signal when the session becomes stale, instead of
  1651. // pooling for stale sessions every second...
  1652. FLastTimeStamp := Now;
  1653. FLock := TIdCriticalSection.Create;
  1654. FContent := TStringList.Create;
  1655. FOwner := AOwner;
  1656. if Assigned(AOwner) then
  1657. begin
  1658. if Assigned(AOwner.OnSessionStart) then begin
  1659. AOwner.OnSessionStart(Self);
  1660. end;
  1661. end;
  1662. end;
  1663. destructor TIdHTTPSession.Destroy;
  1664. begin
  1665. // code added here should also be reflected in
  1666. // the TIdHTTPDefaultSessionList.RemoveSessionFromLockedList method
  1667. // Why? It calls this function and this code gets executed?
  1668. DoSessionEnd;
  1669. FContent.Free;
  1670. FLock.Free;
  1671. if Assigned(FOwner) then begin
  1672. FOwner.RemoveSession(Self);
  1673. end;
  1674. inherited Destroy;
  1675. end;
  1676. procedure TIdHTTPSession.DoSessionEnd;
  1677. begin
  1678. if Assigned(FOwner) and Assigned(FOwner.FOnSessionEnd) then begin
  1679. FOwner.FOnSessionEnd(Self);
  1680. end;
  1681. end;
  1682. function TIdHTTPSession.IsSessionStale: boolean;
  1683. var
  1684. // under ARC, convert a weak reference to a strong reference before working with it
  1685. LOwner: TIdHTTPCustomSessionList;
  1686. begin
  1687. LOwner := FOwner;
  1688. if Assigned(LOwner) then begin
  1689. // TODO: use ticks to keep track of the session's duration instead of using
  1690. // a date/time. Or, at least use a UTC date/time instead of a local date/time...
  1691. Result := TimeStampInterval(FLastTimeStamp, Now) > Integer(LOwner.SessionTimeout);
  1692. end else begin
  1693. Result := True;
  1694. end;
  1695. end;
  1696. procedure TIdHTTPSession.Lock;
  1697. begin
  1698. // ToDo: Add session locking code here
  1699. FLock.Enter;
  1700. end;
  1701. procedure TIdHTTPSession.SetContent(const Value: TStrings);
  1702. begin
  1703. FContent.Assign(Value);
  1704. end;
  1705. procedure TIdHTTPSession.Unlock;
  1706. begin
  1707. // ToDo: Add session unlocking code here
  1708. FLock.Leave;
  1709. end;
  1710. { TIdHTTPRequestInfo }
  1711. constructor TIdHTTPRequestInfo.Create(AOwner: TPersistent);
  1712. begin
  1713. inherited Create(AOwner);
  1714. FCommandType := hcUnknown;
  1715. FCookies := TIdCookies.Create(Self);
  1716. FParams := TStringList.Create;
  1717. ContentLength := -1;
  1718. end;
  1719. procedure TIdHTTPRequestInfo.DecodeAndSetParams(const AValue: String);
  1720. var
  1721. i, j, LLen: Integer;
  1722. s, LCharSet: string;
  1723. LEncoding: IIdTextEncoding;
  1724. begin
  1725. // Convert special characters
  1726. // ampersand '&' separates values {Do not Localize}
  1727. Params.BeginUpdate;
  1728. try
  1729. Params.Clear;
  1730. // TODO: provide an event or property that lets the user specify
  1731. // which charset to use for decoding query string parameters. We
  1732. // should not be using the 'Content-Type' charset for that. For
  1733. // 'application/x-www-form-urlencoded' forms, we should be, though...
  1734. LCharSet := FCharSet;
  1735. if LCharSet = '' then begin
  1736. LCharSet := 'utf-8'; {Do not localize}
  1737. end;
  1738. LEncoding := CharsetToEncoding(LCharSet);//IndyTextEncoding_UTF8;
  1739. i := 1;
  1740. LLen := Length(AValue);
  1741. while i <= LLen do
  1742. begin
  1743. j := i;
  1744. while (j <= LLen) and (AValue[j] <> '&') do {do not localize}
  1745. begin
  1746. Inc(j);
  1747. end;
  1748. s := Copy(AValue, i, j-i);
  1749. // See RFC 1866 section 8.2.1. TP
  1750. s := ReplaceAll(s, '+', ' '); {do not localize}
  1751. // TODO: provide an event or property that lets the user specify
  1752. // which charset to use for converting the decoded Unicode characters
  1753. // to ANSI in pre-Unicode compilers...
  1754. Params.Add(TIdURI.URLDecode(s, LEncoding));
  1755. i := j + 1;
  1756. end;
  1757. finally
  1758. Params.EndUpdate;
  1759. end;
  1760. end;
  1761. destructor TIdHTTPRequestInfo.Destroy;
  1762. begin
  1763. FCookies.Free;
  1764. FParams.Free;
  1765. FPostStream.Free;
  1766. inherited Destroy;
  1767. end;
  1768. function TIdHTTPRequestInfo.IsVersionAtLeast(const AMajor, AMinor: Integer): Boolean;
  1769. begin
  1770. Result := (FVersionMajor > AMajor) or
  1771. ((FVersionMajor = AMajor) and (FVersionMinor >= AMinor));
  1772. end;
  1773. { TIdHTTPResponseInfo }
  1774. procedure TIdHTTPResponseInfo.CloseSession;
  1775. var
  1776. i: Integer;
  1777. LCookie: TIdCookie;
  1778. begin
  1779. i := Cookies.GetCookieIndex(HTTPServer.SessionIDCookieName);
  1780. while i > -1 do begin
  1781. Cookies.Delete(i);
  1782. i := Cookies.GetCookieIndex(HTTPServer.SessionIDCookieName, i);
  1783. end;
  1784. LCookie := Cookies.Add;
  1785. LCookie.CookieName := HTTPServer.SessionIDCookieName;
  1786. LCookie.Expires := Date-7;
  1787. FSession.Free;
  1788. end;
  1789. constructor TIdHTTPResponseInfo.Create(AServer: TIdCustomHTTPServer;
  1790. ARequestInfo: TIdHTTPRequestInfo; AConnection: TIdTCPConnection);
  1791. begin
  1792. inherited Create(AServer);
  1793. FRequestInfo := ARequestInfo;
  1794. FConnection := AConnection;
  1795. FHttpServer := AServer;
  1796. FFreeContentStream := True;
  1797. ResponseNo := GResponseNo;
  1798. ContentType := ''; //GContentType;
  1799. ContentLength := GFContentLength;
  1800. {Some clients may not support folded lines}
  1801. RawHeaders.FoldLines := False;
  1802. FCookies := TIdCookies.Create(Self);
  1803. {TODO Specify version - add a class method dummy that calls version}
  1804. ServerSoftware := GServerSoftware;
  1805. end;
  1806. destructor TIdHTTPResponseInfo.Destroy;
  1807. begin
  1808. FCookies.Free;
  1809. ReleaseContentStream;
  1810. inherited Destroy;
  1811. end;
  1812. procedure TIdHTTPResponseInfo.Redirect(const AURL: string);
  1813. begin
  1814. ResponseNo := 302;
  1815. Location := AURL;
  1816. end;
  1817. procedure TIdHTTPResponseInfo.ReleaseContentStream;
  1818. begin
  1819. if FreeContentStream then begin
  1820. IdDisposeAndNil(FContentStream);
  1821. end else begin
  1822. FContentStream := nil;
  1823. end;
  1824. end;
  1825. procedure TIdHTTPResponseInfo.SetCloseConnection(const Value: Boolean);
  1826. begin
  1827. FCloseConnection := Value;
  1828. Connection := iif(Value, 'close', 'keep-alive'); {Do not Localize}
  1829. end;
  1830. procedure TIdHTTPResponseInfo.SetCookies(const AValue: TIdCookies);
  1831. begin
  1832. FCookies.Assign(AValue);
  1833. end;
  1834. procedure TIdHTTPResponseInfo.SetHeaders;
  1835. var
  1836. LCharset: string;
  1837. I: Integer;
  1838. begin
  1839. inherited SetHeaders;
  1840. if not CloseConnection then begin
  1841. if HTTPServer.KeepAliveTimeout > 0 then begin
  1842. FRawHeaders.Params['Keep-Alive', 'timeout'] := IntToStr(HTTPServer.KeepAliveTimeout); {do not localize}
  1843. end;
  1844. if HTTPServer.KeepAliveMaxRequests > 0 then begin
  1845. FRawHeaders.Params['Keep-Alive', 'max'] := IntToStr(FRequestInfo.FNumRequestsAllowed-1); {do not localize}
  1846. end;
  1847. end;
  1848. if Server <> '' then begin
  1849. FRawHeaders.Values['Server'] := Server; {Do not Localize}
  1850. end;
  1851. if Location <> '' then begin
  1852. FRawHeaders.Values['Location'] := Location; {Do not Localize}
  1853. end;
  1854. if FLastModified > 0 then begin
  1855. FRawHeaders.Values['Last-Modified'] := LocalDateTimeToHttpStr(FLastModified); {do not localize}
  1856. end;
  1857. if FWWWAuthenticate.Count > 0 then begin
  1858. FRawHeaders.Values['WWW-Authenticate'] := ''; {Do not Localize}
  1859. for I := 0 to FWWWAuthenticate.Count-1 do begin
  1860. FRawHeaders.AddValue('WWW-Authenticate', FWWWAuthenticate[I]); {Do not Localize}
  1861. end;
  1862. end
  1863. else if AuthRealm <> '' then begin
  1864. LCharset := AuthCharset;
  1865. if LCharset = '' then begin
  1866. LCharset := 'UTF-8'; {Do not Localize}
  1867. end;
  1868. FRawHeaders.Values['WWW-Authenticate'] := 'Basic realm="' + AuthRealm + '" charset="' + LCharset + '"'; {Do not Localize}
  1869. end;
  1870. if FProxyAuthenticate.Count > 0 then begin
  1871. FRawHeaders.Values['Proxy-Authenticate'] := ''; {Do not Localize}
  1872. for I := 0 to FProxyAuthenticate.Count-1 do begin
  1873. FRawHeaders.AddValue('Proxy-Authenticate', FProxyAuthenticate[I]); {Do not Localize}
  1874. end;
  1875. end
  1876. end;
  1877. procedure TIdHTTPResponseInfo.SetResponseNo(const AValue: Integer);
  1878. begin
  1879. FResponseNo := AValue;
  1880. case FResponseNo of
  1881. // 1XX: Informational
  1882. 100: ResponseText := RSHTTPContinue;
  1883. 101: ResponseText := RSHTTPSwitchingProtocols;
  1884. 102: ResponseText := RSHTTPProcessing;
  1885. 103: ResponseText := RSHTTPEarlyHints;
  1886. //104-199 are Unassigned
  1887. // 2XX: Success
  1888. 200: ResponseText := RSHTTPOK;
  1889. 201: ResponseText := RSHTTPCreated;
  1890. 202: ResponseText := RSHTTPAccepted;
  1891. 203: ResponseText := RSHTTPNonAuthoritativeInformation;
  1892. 204: ResponseText := RSHTTPNoContent;
  1893. 205: ResponseText := RSHTTPResetContent;
  1894. 206: ResponseText := RSHTTPPartialContent;
  1895. 207: ResponseText := RSHTTPMultiStatus;
  1896. 208: ResponseText := RSHTTPAlreadyReported;
  1897. //209-225 are Unassigned
  1898. 226: ResponseText := RSHTTPIMUsed;
  1899. // 227-299 are Unassigned
  1900. // 3XX: Redirections
  1901. 300: ResponseText := RSHTTPMultipleChoices;
  1902. 301: ResponseText := RSHTTPMovedPermanently;
  1903. 302: ResponseText := RSHTTPMovedTemporarily;
  1904. 303: ResponseText := RSHTTPSeeOther;
  1905. 304: ResponseText := RSHTTPNotModified;
  1906. 305: ResponseText := RSHTTPUseProxy;
  1907. // 306 is Unused
  1908. 307: ResponseText := RSHTTPTemporaryRedirect;
  1909. 308: ResponseText := RSHTTPPermanentRedirect;
  1910. // 309-399 are Unassigned
  1911. // 4XX Client Errors
  1912. 400: ResponseText := RSHTTPBadRequest;
  1913. 401: ResponseText := RSHTTPUnauthorized;
  1914. 402: ResponseText := RSHTTPPaymentRequired;
  1915. 403: ResponseText := RSHTTPForbidden;
  1916. 404: begin
  1917. ResponseText := RSHTTPNotFound;
  1918. // Close connection
  1919. CloseConnection := True;
  1920. end;
  1921. 405: ResponseText := RSHTTPMethodNotAllowed;
  1922. 406: ResponseText := RSHTTPNotAcceptable;
  1923. 407: ResponseText := RSHTTPProxyAuthenticationRequired;
  1924. 408: ResponseText := RSHTTPRequestTimeout;
  1925. 409: ResponseText := RSHTTPConflict;
  1926. 410: ResponseText := RSHTTPGone;
  1927. 411: ResponseText := RSHTTPLengthRequired;
  1928. 412: ResponseText := RSHTTPPreconditionFailed;
  1929. 413: ResponseText := RSHTTPRequestEntityTooLong;
  1930. 414: ResponseText := RSHTTPRequestURITooLong;
  1931. 415: ResponseText := RSHTTPUnsupportedMediaType;
  1932. 416: ResponseText := RSHTTPRangeNotSatisfiable;
  1933. 417: ResponseText := RSHTTPExpectationFailed;
  1934. // 418 is Unused
  1935. // 419-420 are Unassigned
  1936. 421: ResponseText := RSHTTPMisdirectedRequest;
  1937. 422: ResponseText := RSHTTPUnprocessableContent;
  1938. 423: ResponseText := RSHTTPLocked;
  1939. 424: ResponseText := RSHTTPFailedDependency;
  1940. 425: ResponseText := RSHTTPTooEarly;
  1941. 426: ResponseText := RSHTTPUpgradeRequired;
  1942. // 427 is Unassigned
  1943. 428: ResponseText := RSHTTPPreconditionRequired;
  1944. 429: ResponseText := RSHTTPTooManyRequests;
  1945. // 430 is Unassigned
  1946. 431: ResponseText := RSHTTPRequestHeaderFieldsTooLarge;
  1947. // 432-499 are Unassigned
  1948. // 5XX Server errors
  1949. 500: ResponseText := RSHTTPInternalServerError;
  1950. 501: ResponseText := RSHTTPNotImplemented;
  1951. 502: ResponseText := RSHTTPBadGateway;
  1952. 503: ResponseText := RSHTTPServiceUnavailable;
  1953. 504: ResponseText := RSHTTPGatewayTimeout;
  1954. 505: ResponseText := RSHTTPHTTPVersionNotSupported;
  1955. 506: ResponseText := RSHTTPVariantAlsoNegotiates;
  1956. 507: ResponseText := RSHTTPInsufficientStorage;
  1957. 508: ResponseText := RSHTTPLoopDetected;
  1958. // 509 is Unassigned
  1959. 510: ResponseText := RSHTTPNotExtended;
  1960. 511: ResponseText := RSHTTPNetworkAuthenticationRequired;
  1961. // 512-599 are Unassigned
  1962. else
  1963. ResponseText := RSHTTPUnknownResponseCode;
  1964. end;
  1965. {if ResponseNo >= 400 then
  1966. // Force Connection closing when there is error during the request processing
  1967. CloseConnection := true;
  1968. end;}
  1969. end;
  1970. function TIdHTTPResponseInfo.ServeFile(AContext: TIdContext; const AFile: String): Int64;
  1971. var
  1972. EnableTransferFile: Boolean;
  1973. begin
  1974. if ContentType = '' then begin
  1975. ContentType := HTTPServer.MIMETable.GetFileMIMEType(AFile);
  1976. end;
  1977. ContentLength := FileSizeByName(AFile);
  1978. if ContentDisposition = '' then begin
  1979. // TODO: use EncodeHeader() here...
  1980. ContentDisposition := IndyFormat('attachment; filename="%s";', [ExtractFileName(AFile)]); {do not localize}
  1981. end;
  1982. WriteHeader;
  1983. EnableTransferFile := not (AContext.Connection.IOHandler is TIdSSLIOHandlerSocketBase);
  1984. Result := AContext.Connection.IOHandler.WriteFile(AFile, EnableTransferFile);
  1985. end;
  1986. function TIdHTTPResponseInfo.SmartServeFile(AContext: TIdContext; const AFile: String): Int64;
  1987. {$IFDEF USE_CLASSINLINE}inline;{$ENDIF}
  1988. begin
  1989. {$I IdDeprecatedImplBugOff.inc}
  1990. Result := SmartServeFile(AContext, FRequestInfo, AFile);
  1991. {$I IdDeprecatedImplBugOn.inc}
  1992. end;
  1993. function TIdHTTPResponseInfo.SmartServeFile(AContext: TIdContext;
  1994. ARequestInfo: TIdHTTPRequestInfo; const AFile: String): Int64;
  1995. var
  1996. LFileDate : TDateTime;
  1997. LReqDate : TDateTime;
  1998. begin
  1999. LFileDate := IndyFileAge(AFile);
  2000. if (LFileDate = 0.0) and (not FileExists(AFile)) then
  2001. begin
  2002. ResponseNo := 404;
  2003. Result := 0;
  2004. end else
  2005. begin
  2006. LReqDate := GMTToLocalDateTime(ARequestInfo.RawHeaders.Values['If-Modified-Since']); {do not localize}
  2007. // if the file date in the If-Modified-Since header is within 2 seconds of the
  2008. // actual file, then we will send a 304. We don't use the ETag - offers nothing
  2009. // over the file date for static files on windows. Linux: consider using iNode
  2010. // RLebeau 2/21/2011: TODO - make use of ETag. It is supposed to be updated
  2011. // whenever the file contents change, regardless of the file's timestamps.
  2012. if (LReqDate <> 0) and (abs(LReqDate - LFileDate) < 2 * (1 / (24 * 60 * 60))) then
  2013. begin
  2014. ResponseNo := 304;
  2015. Result := 0;
  2016. end else
  2017. begin
  2018. Date := Now;
  2019. LastModified := LFileDate;
  2020. Result := ServeFile(AContext, AFile);
  2021. end;
  2022. end;
  2023. end;
  2024. procedure TIdHTTPResponseInfo.WriteContent;
  2025. begin
  2026. if not HeaderHasBeenWritten then begin
  2027. WriteHeader;
  2028. end;
  2029. // RLebeau 11/23/2014: Per RFC 2616 Section 4.3:
  2030. //
  2031. // For response messages, whether or not a message-body is included with
  2032. // a message is dependent on both the request method and the response
  2033. // status code (section 6.1.1). All responses to the HEAD request method
  2034. // MUST NOT include a message-body, even though the presence of entity-
  2035. // header fields might lead one to believe they do. All 1xx
  2036. // (informational), 204 (no content), and 304 (not modified) responses
  2037. // MUST NOT include a message-body. All other responses do include a
  2038. // message-body, although it MAY be of zero length.
  2039. if not (
  2040. (FRequestInfo.CommandType = hcHEAD) or
  2041. ((ResponseNo div 100) = 1) or // informational
  2042. (ResponseNo = 204) or // no content
  2043. (ResponseNo = 304) // not modified
  2044. ) then
  2045. begin
  2046. // Always check ContentText first
  2047. if ContentText <> '' then begin
  2048. FConnection.IOHandler.Write(ContentText, CharsetToEncoding(CharSet));
  2049. end
  2050. else if Assigned(ContentStream) then begin
  2051. // If ContentLength has been assigned then do not send the entire file,
  2052. // in case it grew after WriteHeader() generated the 'Content-Length'
  2053. // header. We cannot exceed the byte count that we told the client
  2054. // we will be sending...
  2055. // TODO: apply this rule to ContentText as well...
  2056. // TODO: do not seek here. Leave the Position where the user left it,
  2057. // in case the user decides to use a custom stream that does not start
  2058. // at Position 0. Send from the current Position onwards.
  2059. if HasContentLength then begin
  2060. if ContentLength > 0 then begin
  2061. ContentStream.Position := 0;
  2062. FConnection.IOHandler.Write(ContentStream, ContentLength, False);
  2063. end;
  2064. end else begin
  2065. ContentStream.Position := 0;
  2066. FConnection.IOHandler.Write(ContentStream);
  2067. end;
  2068. end
  2069. else begin
  2070. FConnection.IOHandler.Write('<HTML><BODY><B>' + IntToStr(ResponseNo) + ' ' + ResponseText {Do not Localize}
  2071. + '</B></BODY></HTML>', CharsetToEncoding(CharSet)); {Do not Localize}
  2072. end;
  2073. end;
  2074. // Clear All - This signifies that WriteConent has been called.
  2075. ContentText := ''; {Do not Localize}
  2076. ReleaseContentStream;
  2077. end;
  2078. procedure TIdHTTPResponseInfo.WriteHeader;
  2079. var
  2080. i: Integer;
  2081. LBufferingStarted: Boolean;
  2082. LCharSet: string;
  2083. begin
  2084. if HeaderHasBeenWritten then begin
  2085. raise EIdHTTPHeaderAlreadyWritten.Create(RSHTTPHeaderAlreadyWritten);
  2086. end;
  2087. FHeaderHasBeenWritten := True;
  2088. if AuthRealm <> '' then
  2089. begin
  2090. ResponseNo := 401;
  2091. if (ContentText = '') and (not Assigned(ContentStream)) then
  2092. begin
  2093. ContentType := 'text/html; charset=utf-8'; {Do not Localize}
  2094. ContentText := '<HTML><BODY><B>' + IntToStr(ResponseNo) + ' ' + ResponseText + '</B></BODY></HTML>'; {Do not Localize}
  2095. ContentLength := -1; // calculated below
  2096. end;
  2097. end;
  2098. // RLebeau 5/15/2012: for backwards compatibility. We really should
  2099. // make the user set this every time instead...
  2100. if ContentType = '' then begin
  2101. if (ContentText <> '') or Assigned(ContentStream) then begin
  2102. LCharSet := FCharSet;
  2103. if LCharSet = '' then begin
  2104. LCharSet := 'utf-8'; {Do not Localize}
  2105. end;
  2106. ContentType := 'text/html; charset=' + LCharSet; {Do not Localize}
  2107. end;
  2108. end;
  2109. // RLebeau: according to RFC 2616 Section 4.4:
  2110. //
  2111. // If a Content-Length header field (section 14.13) is present, its
  2112. // decimal value in OCTETs represents both the entity-length and the
  2113. // transfer-length. The Content-Length header field MUST NOT be sent
  2114. // if these two lengths are different (i.e., if a Transfer-Encoding
  2115. // header field is present). If a message is received with both a
  2116. // Transfer-Encoding header field and a Content-Length header field,
  2117. // the latter MUST be ignored.
  2118. // ...
  2119. // Messages MUST NOT include both a Content-Length header field and a
  2120. // non-identity transfer-coding. If the message does include a non-
  2121. // identity transfer-coding, the Content-Length MUST be ignored.
  2122. if (ContentLength = -1) and
  2123. ((TransferEncoding = '') or TextIsSame(TransferEncoding, 'identity')) then {do not localize}
  2124. begin
  2125. if not (
  2126. (FRequestInfo.CommandType = hcHEAD) or
  2127. ((ResponseNo div 100) = 1) or // informational
  2128. (ResponseNo = 204) or // no content
  2129. (ResponseNo = 304) // not modified
  2130. ) then
  2131. begin
  2132. // Always check ContentText first
  2133. if ContentText <> '' then begin
  2134. ContentLength := CharsetToEncoding(CharSet).GetByteCount(ContentText);
  2135. end
  2136. else if Assigned(ContentStream) then begin
  2137. // TODO: take the current Position into account, in case the user decides
  2138. // to use a custom stream that does not start at Position 0. Send data
  2139. // from the current Position onwards.
  2140. ContentLength := ContentStream.Size;
  2141. end else begin
  2142. ContentType := 'text/html; charset=utf-8'; {Do not Localize}
  2143. ContentText := '<HTML><BODY><B>' + IntToStr(ResponseNo) + ' ' + ResponseText + '</B></BODY></HTML>'; {Do not Localize}
  2144. ContentLength := CharsetToEncoding(CharSet).GetByteCount(ContentText);
  2145. end;
  2146. end else begin
  2147. ContentLength := 0;
  2148. end;
  2149. end;
  2150. if Date <= 0 then begin
  2151. Date := Now;
  2152. end;
  2153. SetHeaders;
  2154. LBufferingStarted := not FConnection.IOHandler.WriteBufferingActive;
  2155. if LBufferingStarted then begin
  2156. FConnection.IOHandler.WriteBufferOpen;
  2157. end;
  2158. try
  2159. // Write HTTP status response
  2160. // TODO: if the client sent an HTTP/1.0 request, send an HTTP/1.0 response?
  2161. FConnection.IOHandler.WriteLn('HTTP/1.1 ' + IntToStr(ResponseNo) + ' ' + ResponseText); {Do not Localize}
  2162. // Write headers
  2163. FConnection.IOHandler.Write(RawHeaders);
  2164. // Write cookies
  2165. for i := 0 to Cookies.Count - 1 do begin
  2166. FConnection.IOHandler.WriteLn('Set-Cookie: ' + Cookies[i].ServerCookie); {Do not Localize}
  2167. end;
  2168. // HTTP headers end with a double CR+LF
  2169. FConnection.IOHandler.WriteLn;
  2170. if LBufferingStarted then begin
  2171. FConnection.IOHandler.WriteBufferClose;
  2172. end;
  2173. except
  2174. if LBufferingStarted then begin
  2175. FConnection.IOHandler.WriteBufferCancel;
  2176. end;
  2177. raise;
  2178. end;
  2179. end;
  2180. function TIdHTTPResponseInfo.GetServer: string;
  2181. begin
  2182. Result := Server;
  2183. end;
  2184. procedure TIdHTTPResponseInfo.SetServer(const Value: string);
  2185. begin
  2186. Server := Value;
  2187. end;
  2188. { TIdHTTPDefaultSessionList }
  2189. constructor TIdHTTPDefaultSessionList.Create(AOwner: TComponent);
  2190. var
  2191. LList: TIdHTTPSessionList;
  2192. begin
  2193. inherited Create(AOwner);
  2194. FSessionList := TIdHTTPSessionThreadList.Create;
  2195. LList := FSessionList.LockList;
  2196. try
  2197. LList.Capacity := SessionCapacity;
  2198. finally
  2199. FSessionList.UnlockList;
  2200. end;
  2201. end;
  2202. destructor TIdHTTPDefaultSessionList.Destroy;
  2203. begin
  2204. Clear;
  2205. FSessionList.Free;
  2206. inherited Destroy;
  2207. end;
  2208. procedure TIdHTTPDefaultSessionList.Add(ASession: TIdHTTPSession);
  2209. begin
  2210. SessionList.Add(ASession);
  2211. end;
  2212. procedure TIdHTTPDefaultSessionList.Clear;
  2213. var
  2214. LSessionList: TIdHTTPSessionList;
  2215. LSession: TIdHTTPSession;
  2216. i: Integer;
  2217. begin
  2218. LSessionList := SessionList.LockList;
  2219. try
  2220. for i := LSessionList.Count - 1 DownTo 0 do
  2221. begin
  2222. LSession := {$IFDEF HAS_GENERICS_TList}LSessionList[i]{$ELSE}TIdHTTPSession(LSessionList[i]){$ENDIF};
  2223. if LSession <> nil then
  2224. begin
  2225. LSession.DoSessionEnd;
  2226. // must set the owner to nil or the session will try to fire the
  2227. // OnSessionEnd event again, and also remove itself from the session
  2228. // list and deadlock
  2229. LSession.FOwner := nil;
  2230. LSession.Free;
  2231. end;
  2232. end;
  2233. LSessionList.Clear;
  2234. LSessionList.Capacity := SessionCapacity;
  2235. finally
  2236. SessionList.UnlockList;
  2237. end;
  2238. end;
  2239. function TIdHTTPDefaultSessionList.CreateSession(const RemoteIP, SessionID: String): TIdHTTPSession;
  2240. begin
  2241. Result := TIdHTTPSession.CreateInitialized(Self, SessionID, RemoteIP);
  2242. SessionList.Add(Result);
  2243. end;
  2244. function TIdHTTPDefaultSessionList.CreateUniqueSession(
  2245. const RemoteIP: String): TIdHTTPSession;
  2246. var
  2247. SessionID: String;
  2248. begin
  2249. SessionID := GetRandomString(15);
  2250. // TODO: shouldn't this lock the SessionList before entering the
  2251. // loop to prevent race conditions across multiple threads?
  2252. {SessionList.LockList;
  2253. try}
  2254. while GetSession(SessionID, RemoteIP) <> nil do
  2255. begin
  2256. SessionID := GetRandomString(15);
  2257. end; // while
  2258. Result := CreateSession(RemoteIP, SessionID);
  2259. {finally
  2260. SessionList.UnlockList;
  2261. end;}
  2262. end;
  2263. function TIdHTTPDefaultSessionList.GetSession(const SessionID, RemoteIP: string): TIdHTTPSession;
  2264. var
  2265. LSessionList: TIdHTTPSessionList;
  2266. LSession: TIdHTTPSession;
  2267. i: Integer;
  2268. begin
  2269. Result := nil;
  2270. LSessionList := SessionList.LockList;
  2271. try
  2272. // get current time stamp
  2273. for i := 0 to LSessionList.Count - 1 do
  2274. begin
  2275. LSession := TIdHTTPSession(LSessionList[i]);
  2276. // the stale sessions check has been removed... the cleanup thread should suffice plenty
  2277. if Assigned(LSession) and TextIsSame(LSession.FSessionID, SessionID) and
  2278. ((RemoteIP = '') or TextIsSame(LSession.RemoteHost, RemoteIP)) then
  2279. begin
  2280. // Session found
  2281. // TODO: use a timer to signal when the session becomes stale, instead of
  2282. // pooling for stale sessions every second...
  2283. LSession.FLastTimeStamp := Now;
  2284. Result := LSession;
  2285. Break;
  2286. end;
  2287. end;
  2288. finally
  2289. SessionList.UnlockList;
  2290. end;
  2291. end;
  2292. procedure TIdHTTPDefaultSessionList.PurgeStaleSessions(PurgeAll: Boolean = false);
  2293. var
  2294. LSessionList: TIdHTTPSessionList;
  2295. LSession: TIdHTTPSession;
  2296. i: Integer;
  2297. begin
  2298. // S.G. 24/11/00: Added a way to force a session purge (Used when thread is terminated)
  2299. // Get necessary data
  2300. Assert(SessionList<>nil);
  2301. LSessionList := SessionList.LockList;
  2302. try
  2303. // Loop though the sessions.
  2304. for i := LSessionList.Count - 1 downto 0 do
  2305. begin
  2306. // Identify the stale sessions
  2307. LSession := {$IFDEF HAS_GENERICS_TList}LSessionList[i]{$ELSE}TIdHTTPSession(LSessionList[i]){$ENDIF};
  2308. if Assigned(LSession) and (PurgeAll or LSession.IsSessionStale) then
  2309. begin
  2310. RemoveSessionFromLockedList(i, LSessionList);
  2311. end;
  2312. end;
  2313. finally
  2314. SessionList.UnlockList;
  2315. end;
  2316. end;
  2317. procedure TIdHTTPDefaultSessionList.RemoveSession(Session: TIdHTTPSession);
  2318. var
  2319. LSessionList: TIdHTTPSessionList;
  2320. Index: integer;
  2321. begin
  2322. LSessionList := SessionList.LockList;
  2323. try
  2324. Index := LSessionList.IndexOf(Session);
  2325. if index > -1 then
  2326. begin
  2327. LSessionList.Delete(index);
  2328. end;
  2329. finally
  2330. SessionList.UnlockList;
  2331. end;
  2332. end;
  2333. procedure TIdHTTPDefaultSessionList.RemoveSessionFromLockedList(AIndex: Integer;
  2334. ALockedSessionList: TIdHTTPSessionList);
  2335. var
  2336. LSession: TIdHTTPSession;
  2337. begin
  2338. LSession := {$IFDEF HAS_GENERICS_TList}ALockedSessionList[AIndex]{$ELSE}TIdHTTPSession(ALockedSessionList[AIndex]){$ENDIF};
  2339. LSession.DoSessionEnd;
  2340. // must set the owner to nil or the session will try to fire the OnSessionEnd
  2341. // event again, and also remove itself from the session list and deadlock
  2342. LSession.FOwner := nil;
  2343. LSession.Free;
  2344. ALockedSessionList.Delete(AIndex);
  2345. end;
  2346. { TIdHTTPSessionClearThread }
  2347. procedure TIdHTTPSessionCleanerThread.AfterRun;
  2348. var
  2349. // under ARC, convert a weak reference to a strong reference before working with it
  2350. LSessionList: TIdHTTPCustomSessionList;
  2351. begin
  2352. LSessionList := FSessionList;
  2353. if Assigned(LSessionList) then begin
  2354. LSessionList.PurgeStaleSessions(True);
  2355. {$IFDEF USE_OBJECT_ARC}LSessionList := nil;{$ENDIF}
  2356. end;
  2357. inherited AfterRun;
  2358. end;
  2359. constructor TIdHTTPSessionCleanerThread.Create(SessionList: TIdHTTPCustomSessionList);
  2360. begin
  2361. inherited Create(False);
  2362. // thread priority used to be set to tpIdle but this is not supported
  2363. // under DotNet. How low do you want to go?
  2364. IndySetThreadPriority(Self, tpLowest);
  2365. FSessionList := SessionList;
  2366. end;
  2367. procedure TIdHTTPSessionCleanerThread.Run;
  2368. var
  2369. // under ARC, convert a weak reference to a strong reference before working with it
  2370. LSessionList: TIdHTTPCustomSessionList;
  2371. begin
  2372. // TODO: use a timer to signal when sessions becomes stale, instead of
  2373. // pooling for stale sessions every second...
  2374. IndySleep(1000);
  2375. LSessionList := FSessionList;
  2376. if Assigned(LSessionList) then begin
  2377. LSessionList.PurgeStaleSessions(Terminated);
  2378. end;
  2379. end;
  2380. end.