IdCustomHTTPServer.pas 92 KB

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