SynHttpSrv.pas 87 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333
  1. {--------------------------------------------------------------}
  2. { SynHttpSrv.pas - HTTP server over Synapse }
  3. { Author: Semi }
  4. { Started: 070528 }
  5. {--------------------------------------------------------------}
  6. unit SynHttpSrv;
  7. {$IFDEF FPC}
  8. {$MODE Delphi}
  9. {$ENDIF}
  10. interface
  11. uses
  12. {$IFDEF MSWINDOWS}
  13. Windows,
  14. {$ELSE}
  15. SynaUtil,
  16. {$ENDIF}
  17. SysUtils,
  18. Classes,
  19. blcksock,
  20. SynSrv;
  21. //-------------------------------------------------------------
  22. {$undef DEBUG}
  23. //{$define DEBUG}
  24. type
  25. // Result: True=found/stop, False=continue
  26. THeaderEnum = function(const Value: string; LParam: NativeUInt): boolean of object;
  27. THeaderList = class(TStringList)
  28. private
  29. function GetValueByName(const Name: string): string;
  30. procedure SetValueByName(const Name, Value: string);
  31. function GetNameByIndex(Index: integer): string;
  32. function GetValueByIndex(Index: integer): string;
  33. function CheckHttpFindValue(const Value: string; LParam: NativeUInt): boolean;
  34. function GetSubValue(const Name, SubName: string): string;
  35. procedure SetSubValue(const Name, SubName, Value: string);
  36. protected
  37. procedure Put(Index: integer; const S: string); override;
  38. public
  39. property Values[const Name: string]: string Read GetValueByName Write SetValueByName; default;
  40. //
  41. property Names[Index: integer]: string Read GetNameByIndex;
  42. property ValuesByIndex[Index: integer]: string Read GetValueByIndex;
  43. property SubValues[const Name, SubName: string]: string Read GetSubValue Write SetSubValue;
  44. // for 'ContentType: text/html; charset="Windows-1250"', SubValues['Content-Type','charset']
  45. //
  46. function IndexOfName(const Name: string): integer; reintroduce;
  47. procedure AddValue(const Name, Value: string); // add (possibly duplicate) value...
  48. function RemoveValue(const Name: string): boolean; // used also by writing Values[Name]:='';
  49. //
  50. // Enumerates duplicated or comma-separated headers:
  51. procedure EnumHeaders(const Name: string; const Enum: THeaderEnum; const Sep: char; LParam: NativeUInt = 0);
  52. function HasValue(const Name, Value: string): boolean; // Connection: upgrade, close
  53. function Add(const S: string): integer; override;
  54. procedure Insert(Index: integer; const S: string); override;
  55. end;
  56. THttpCookie = class(TCollectionItem)
  57. private
  58. FName: string;
  59. FValue: string;
  60. FDomain: string;
  61. FPath: string;
  62. FExpires: string;
  63. FVersion: string;
  64. FMaxAge: string;
  65. FComment: string;
  66. FSecure: boolean;
  67. FSameSite:boolean;
  68. function GetText: string;
  69. public
  70. property Name: string Read FName Write FName;
  71. property Value: string Read FValue Write FValue;
  72. property Text: string Read GetText;
  73. //
  74. property Domain: string Read FDomain Write FDomain;
  75. property Path: string Read FPath Write FPath;
  76. property Version: string Read FVersion Write FVersion;
  77. property MaxAge: string Read FMaxAge Write FMaxAge;
  78. property Comment: string Read FComment Write FComment;
  79. property Secure: boolean Read FSecure Write FSecure;
  80. property SameSite: boolean Read FSameSite Write FSameSite;
  81. property Expires: string Read FExpires Write FExpires; // obsolette...
  82. //
  83. procedure DeleteCookie; // set MaxAge:='0'; so that client will delete the cookie...
  84. //
  85. procedure Assign(Source: TPersistent); override;
  86. //
  87. function GetServerCookie: string; // Set-Cookie: format... (for sending server->client)
  88. function GetClientCookie: string; // Cookie: format... (for sending client->server)
  89. function ParseValue(Line: string; Version: NativeUInt): boolean;
  90. // parse either Cookie: or SetCookie: header part, 1 cookie at a time...
  91. function MatchPath(const aPath: string): boolean; // is it cookie for this path?
  92. end;
  93. { THttpCookies }
  94. THttpCookies = class(TCollection)
  95. private
  96. function GetCookieItem(Index: integer): THttpCookie;
  97. function AddCookieValue(const Value: string; LParam: NativeUInt): boolean;
  98. function GetValue(const Name: string): string;
  99. procedure SetValue(const Name, Value: string);
  100. function GetCommaText: string;
  101. public
  102. constructor Create;
  103. //
  104. property Cookies[Index: integer]: THttpCookie Read GetCookieItem; default;
  105. function IndexOf(const Name: string): integer;
  106. function Find(const Name: string): THttpCookie;
  107. //
  108. // Load cookies from client, used in server... (Cookie: headers)
  109. procedure LoadClientCookies(Headers: THeaderList);
  110. // Save cookies to client, used in server...
  111. procedure SaveServerCookies(Headers: THeaderList; const DefaultDomain, DefaultPath: string);
  112. //
  113. // Load cookies from server, used in client... (Set-Cookie: headers)
  114. procedure LoadServerCookies(Headers: THeaderList);
  115. // Save cookies to server, used in client...
  116. procedure SaveClientCookies(Headers: THeaderList; const Path: string);
  117. //
  118. // Other client-side functions:
  119. procedure MergeCookies(Source: THttpCookies);
  120. procedure SetDefaultPath;
  121. procedure SetSameSite;
  122. property Values[const Name: string]: string Read GetValue Write SetValue;
  123. property CommaText: string Read GetCommaText;
  124. end;
  125. // HTTP request and response object
  126. { THttpRequest }
  127. THttpRequest = class(TPersistent)
  128. private
  129. FHeaders: THeaderList;
  130. FCookies: THttpCookies;
  131. FParams: TStringList;
  132. FPostStream: TStream;
  133. FUrl: string;
  134. FMethod: string;
  135. FProtocol: string;
  136. FContent: string;
  137. //FContentStream: TStream;
  138. FStatusCode: integer;
  139. FStatusMsg: string;
  140. FConnection: TObject;
  141. FFlags: integer;
  142. FResponseSent: boolean;
  143. FCharSet: string;
  144. FDocument: string;
  145. procedure SetHeaders(Value: THeaderList);
  146. procedure SetCookies(Value: THttpCookies);
  147. procedure SetStatusCode(Value: integer);
  148. function GetFlagBool(Index: integer): boolean;
  149. procedure SetFlagBool(Index: integer; Value: boolean);
  150. function GetStrProp(Index: integer): string;
  151. procedure SetStrProp(Index: integer; const Value: string);
  152. function GetDateProp(Index: integer): TDateTime;
  153. procedure SetDateProp(Index: integer; const Value: TDateTime);
  154. //
  155. procedure ApplyHeaders(bnIsServer: boolean); virtual;
  156. // parse Cookies and possibly other things from Headers... used by TSynHttpServer.ReadRequest
  157. function AddMultiPartFormItem(Headers: THeaderList; const FieldName, Content: string): boolean;
  158. procedure SetCharSet(const Value: string);
  159. public
  160. constructor Create;
  161. destructor Destroy; override;
  162. procedure Assign(Source: TPersistent); override;
  163. //
  164. property Headers: THeaderList Read FHeaders Write SetHeaders; // Set assigns copy...
  165. //
  166. property Cookies: THttpCookies Read FCookies Write SetCookies; // Set assigns copy...
  167. //
  168. property Url: string Read FUrl; // '/index.html'
  169. property Document: string Read FDocument;
  170. property Method: string Read FMethod; // 'GET'
  171. property Protocol: string Read FProtocol; // 'HTTP/1.1'
  172. // also MUST include Headers['Host'] value...
  173. //
  174. property StatusCode: integer Read FStatusCode Write SetStatusCode; // 200
  175. property StatusMsg: string Read FStatusMsg Write FStatusMsg; // 'OK'
  176. //
  177. property Content: string Read FContent Write FContent;
  178. //property ContentStream: TStream Read FContentStream Write FContentStream; // stream is owned by the Request...
  179. property SendChunked: boolean index 1 Read GetFlagBool Write SetFlagBool;
  180. // set to True to prevent asking Stream.Size and send in chunked mode (without Content-length)
  181. //
  182. property Connection: TObject Read FConnection Write FConnection; // TSynTcpSrvConnection usually...
  183. //
  184. // Params contain 'Name=Value' for parameters in ?params in url and for POST params inside content:
  185. // When posting files, Params does NOT contain file data, only FileName, use GetPostFormParam to retrieve file data...
  186. property Params: TStringList Read FParams; // use Request.Params.Values[ParamName]
  187. property PostStream: TStream Read FPostStream Write FPostStream;
  188. function GetPostFormParam(const ParamName: string; var ParamData: string): boolean;
  189. // get 1 param from multipart/form-data or application/x-www-form-urlencoded...
  190. //
  191. // Common operations for application for making reply:
  192. procedure ServeFile(const LocalFileName: string);
  193. // open file in ContentStream, set Last-Modified, Content-Length, Content-Type
  194. procedure Redirect(const aUrl: string); // set 302 redirection and Location: header
  195. //
  196. // Functions used by server/client:
  197. procedure ParseFirstRequestLine(Line: string); // parse: 'GET /index.html HTTP/1.1' // used by server
  198. procedure ParseFirstResponseLine(Line: string); // parse: 'HTTP/1.1 200 OK' // used by client
  199. function GetFirstResponseLine: string; // format: 'HTTP/1.1 200 OK' // used by server
  200. function GetFirstRequestLine: string; // format: 'GET /index.html HTTP/1.1' // used by client
  201. procedure ParsePostFormData;
  202. // parse Content string into Params, used usually by Server (for POST requests with propper Content-Type)
  203. //
  204. function MatchTag(Etags: string): boolean;
  205. // Etags may have multiple tags, comma-separated... returns True, if some of them is identical with Etag...
  206. //
  207. // Common Header properties:
  208. property ContentType: string index 0 Read GetStrProp Write SetStrProp; // 'text/html; charset="Windows-1250"'
  209. property BaseContentType: string index 1 Read GetStrProp; // 'text/html'
  210. property CharSet: string Read FCharSet Write SetCharSet;
  211. property ContentDisposition: string index 2 Read GetStrProp Write SetStrProp;
  212. // 'attachment; filename=targetfile.html'
  213. property TargetFileName: string index 3 Read GetStrProp Write SetStrProp;
  214. // name, by which this should be saved by client (in Content-Disposition)
  215. property Location: string index 4 Read GetStrProp Write SetStrProp; // Location: header
  216. property Etag: string index 5 Read GetStrProp Write SetStrProp;
  217. // Etag is used for caches, so that they may know, that their copy is exactly identical with current data (having same Etag for same URL means it is exactly identical...)
  218. property Host: string index 6 Read GetStrProp Write SetStrProp; // must be in Request
  219. property Referer: string index 7 Read GetStrProp Write SetStrProp;
  220. property UserAgent: string index 8 Read GetStrProp Write SetStrProp;
  221. property Vary: string index 9 Read GetStrProp Write SetStrProp;
  222. // list of headers, for which the response varies... used by caches...
  223. property WwwAuthenticate: string index 10 Read GetStrProp Write SetStrProp;
  224. // authentication challenge, used with 401 status-code... see RFC2617...
  225. property Authorization: string index 11 Read GetStrProp Write SetStrProp; // Authorization: value, sent by client
  226. property Boundary: string index 12 Read GetStrProp Write SetStrProp;
  227. // Content-Type: multipart/any; boundary=0123456789
  228. property ContentEncoding: string index 13 Read GetStrProp Write SetStrProp;
  229. property CacheControl: string index 14 Read GetStrProp Write SetStrProp;
  230. property Pragma: string index 15 Read GetStrProp Write SetStrProp;
  231. property ServerSoftware: string index 16 Read GetStrProp Write SetStrProp;
  232. property AcceptEncoding: string index 17 Read GetStrProp Write SetStrProp;
  233. property ContentLength: string index 18 Read GetStrProp Write SetStrProp;
  234. property TransferEncoding: string index 19 Read GetStrProp Write SetStrProp;
  235. //
  236. property Date: TDateTime index 0 Read GetDateProp Write SetDateProp;
  237. // local date of serving the request (is converted to UTC) (filled by Server)
  238. property LastModified: TDateTime index 1 Read GetDateProp Write SetDateProp;
  239. // local date of file modification (is converted to UTC) (filled by ServeFile method)
  240. property LastModifiedUTC: TDateTime index 2 Read GetDateProp Write SetDateProp;
  241. // UTC date of file modification (filled by ServeFile method)
  242. property Expires: TDateTime index 3 Read GetDateProp Write SetDateProp;
  243. // UTC date of expiration (for caches, allows caching of otherwise-non-cacheable responses)
  244. property ResponseSent: boolean Read FResponseSent Write FResponseSent;
  245. end;
  246. TSynOnHttpGet = procedure(Sender: TObject; Connection: TSynTcpSrvConnection;
  247. Request, Response: THttpRequest) of object;
  248. TSynOnHttpExpect = procedure(Sender: TObject; Request: THttpRequest; var bnContinue: boolean) of object;
  249. TSynHTTPCreatePostStream = procedure(Sender: TObject; Request: THttpRequest; var PostStream: TStream) of object;
  250. // Virtual HTTP server.
  251. // This level does some RFC2616 stuff for you,
  252. // but it does NOT resolve URL->filename, which must be done in OnHttpGet method.
  253. { TSynHttpServer }
  254. TSynHttpServer = class(TSynTcpServer)
  255. private
  256. FOnCreatePostStream: TSynHTTPCreatePostStream;
  257. FOnHttpGet: TSynOnHttpGet;
  258. FOnExpect: TSynOnHttpExpect;
  259. FCertFile: string;
  260. FKeyFile: string;
  261. FKeyPass: string;
  262. FCaCertFile: string;
  263. procedure HandleClientCommand(Connection: TSynTcpSrvConnection; Command: string);
  264. procedure CreatePostStream(Request: THttpRequest);
  265. protected
  266. procedure ReadRequest(Connection: TSynTcpSrvConnection; Request, Reply: THttpRequest; Command: string); virtual;
  267. procedure DoHttpGet(Connection: TSynTcpSrvConnection; Request, Reply: THttpRequest); virtual;
  268. procedure SetActive(Value: boolean); override;
  269. public
  270. constructor Create(AOwner: TComponent); override;
  271. //
  272. procedure InitHttps(const CertFile, KeyFile, KeyPassword, CaCertFile: string);
  273. procedure SendReply(Connection: TSynTcpSrvConnection; Request, Reply: THttpRequest); virtual;
  274. //
  275. published
  276. property Port;//default '80';
  277. //
  278. property OnHttpGet: TSynOnHttpGet Read FOnHttpGet Write FOnHttpGet;
  279. property OnExpect: TSynOnHttpExpect Read FOnExpect Write FOnExpect;
  280. property OnCreatePostStream: TSynHTTPCreatePostStream Read FOnCreatePostStream Write FOnCreatePostStream;
  281. end;
  282. var
  283. // Value for Server: header...
  284. ServerValue: string = 'SynHttpSrv/1.0';
  285. function ReadHeadersFromSocket(Socket: TTCPBlockSocket; Headers: THeaderList; LineTimeout: integer = 0): boolean;
  286. function SendSocketStream(Socket: TTcpBlockSocket; Stream: TStream; MaxSize: int64 = -1;
  287. bnHttpChunked: boolean = False): boolean;
  288. const
  289. cProtoHttp10 = 'HTTP/1.0';
  290. cProtoHttp11 = 'HTTP/1.1';
  291. function GetHttpStatusMsg(StatusCode: integer; var StatusMsg: string): boolean;
  292. //-----------------------------------------------------------------------------
  293. // string utility functions:
  294. // Trim(Copy(S,Pos,Count));
  295. function TrimCopy(const S: string; Pos, Count: integer): string;
  296. // trim inplace:
  297. procedure DoTrim(var S: string);
  298. // remove first token, no quoting:
  299. function FetchToken(var Line: string; const Sep: string; bnTrim: boolean): string;
  300. // "Quote value, using \" and \\ inside..."
  301. function QuoteValue(const Value: string): string;
  302. // remove first comma-separated value, possibly quoted
  303. function FetchQSepValue(var Line: string; const Sep: string): string;
  304. // for parsing: remove first Name="Value", separators either ";" or ","
  305. function FetchDequoted(var Line: string; out Name, Value: string): boolean;
  306. // get value from Name="Value" in multi-prop header value: (from 'text/html; charset="Windows-1250"' can extract charset...)
  307. function GetHeaderSubValue(Header: string; const Name: string): string;
  308. procedure ReplaceHeaderSubValue(var Header: string; const Name, Value: string);
  309. function CombineStrings(Strings: TStrings; const Separator: string): string;
  310. // SameHead == SameText(Copy(Str,1,Length(SHead)),SHead)
  311. function SameHead(const Str, SHead: string): boolean;
  312. // multipart parsing...
  313. type
  314. // Result: True=found/stop, False=continue
  315. TMultipartEnumCallback = function(Headers: THeaderList; const FieldName, Content: string): boolean of object;
  316. procedure EnumMultiPart(ContentData, Boundary: string; const Enum: TMultipartEnumCallback);
  317. // Date - in HTTP (RFC2616), all dates MUST be in GMT (utc) format...
  318. function FormatHttpDate(LocalDate: TDateTime; bnIsLocal: boolean): string;
  319. function ParseHttpDate(Str: string; out DateTime: TDateTime): boolean;
  320. function LocalToUtcDateTime(LocalDate: TDateTime): TDateTime;
  321. function UtcToLocalDateTime(UtcDate: TDateTime): TDateTime;
  322. function TimeZoneBiasTime: TDateTime;
  323. function GetFileDateUtc(const FileName: string): TDateTime;
  324. // Content-Type detection used by THttpRequest.ServeFile
  325. function DetectContentType(const FileName: string): string;
  326. function GetContentTypeByExt(const Ext: string): string;
  327. // RegisterContentType can be used to register content-types by extension from user configuration:
  328. procedure RegisterContentType(const Ext, ContentType: string);
  329. {$ifdef MSWINDOWS}
  330. // Automatically register content-types for all file extensions from registry...
  331. procedure RegisterContentTypesFromRegistry;
  332. {$endif MSWINDOWS}
  333. // convert 'Documents%20and%20Settings' to 'Documents and Settings', also handles utf8 encoded in %C4%8D...
  334. function ConvertUrlChars(Url: string): string;
  335. procedure TryDecodeUtf8(var Url: string); // used by ConvertUrlChars...
  336. var
  337. // location of /error.html file, used by THttpRequest.ServerFile:
  338. Error404Url: string;
  339. // contents of 404 error doc, used by THttpRequest.ServerFile, only if Error404Url is empty:
  340. Error404DocText: string;
  341. procedure Register;
  342. implementation
  343. procedure Register;
  344. begin
  345. RegisterComponents('Samples', [TSynHttpServer]);
  346. end;
  347. function SendSocketStream(Socket: TTcpBlockSocket; Stream: TStream; MaxSize: int64; bnHttpChunked: boolean): boolean;
  348. var
  349. Buffer: array[0..16383] of char;
  350. BlockSize, Size: integer;
  351. label
  352. _Complete;
  353. begin
  354. if (MaxSize < 0) then
  355. MaxSize := $10000000000; // 16Gb...
  356. //
  357. // Send Stream, without asking its Size... This allows sending from TDecompressionStream etc...
  358. BlockSize := Socket.SendMaxChunk;
  359. if (BlockSize > SizeOf(Buffer)) then
  360. BlockSize := SizeOf(Buffer); // no real need to send >4k packets...
  361. //
  362. while True do
  363. begin
  364. if (BlockSize > MaxSize) then
  365. begin
  366. // Last block...
  367. if (MaxSize = 0) then
  368. begin
  369. Result := True;
  370. goto _Complete;
  371. end;
  372. BlockSize := MaxSize;
  373. end;
  374. //
  375. Size := Stream.Read(Buffer[0], BlockSize);
  376. if (Size <= 0) then
  377. begin
  378. // EOF
  379. Result := (Size = 0); // stream complete...
  380. _Complete:
  381. if Result and bnHttpChunked then
  382. begin
  383. Socket.SendString('0'#13#10#13#10);
  384. Result := True;
  385. end;
  386. exit;
  387. end;
  388. //
  389. if bnHttpChunked then
  390. Socket.SendString(UTF8Encode(Format('%x'#13#10, [Size])));
  391. //
  392. Socket.SendBuffer(@Buffer, Size);
  393. if (Socket.LastError <> 0) then
  394. break;
  395. end;
  396. // Failed due to LastError
  397. Result := False;
  398. end;
  399. // read header lines until empty line is received...
  400. function ReadHeadersFromSocket(Socket: TTCPBlockSocket; Headers: THeaderList; LineTimeout: integer): boolean;
  401. var
  402. Line: string;
  403. begin
  404. if (LineTimeout = 0) then
  405. LineTimeout := SynSrv.cDefLineTimeout; // default 2 minutes...
  406. //
  407. while True do
  408. begin
  409. Line := string(Socket.RecvString(LineTimeout));
  410. if (Line = '') then
  411. begin
  412. if (Socket.LastError <> 0) then
  413. begin
  414. // error (either timeout or client disconnected)
  415. Result := False;
  416. exit;
  417. end;
  418. // Headers complete (terminated by empty line)
  419. {$ifdef DEBUG}
  420. Debug('Request headers:'#13#10'%s',[Headers.Text]);
  421. {$endif DEBUG}
  422. Result := True;
  423. exit;
  424. end;
  425. Headers.Add(Line);
  426. end;
  427. end;
  428. function TrimCopy(const S: string; Pos, Count: integer): string;
  429. var
  430. len, maxlen: integer;
  431. begin
  432. //Result:=Trim(Copy(S,Pos,Count));
  433. // Optimized - trim before allocating result:
  434. len := Length(S);
  435. while (Pos <= len) and (S[Pos] <= ' ') do
  436. Inc(Pos);
  437. if (Pos <= len) then
  438. begin
  439. maxlen := len - Pos + 1;
  440. if (Count > maxlen) then
  441. Count := maxlen;
  442. while (Count > 0) and (S[Pos + Count - 1] <= ' ') do
  443. Dec(Count);
  444. end;
  445. Result := Copy(S, Pos, Count);
  446. end;
  447. procedure DoTrim(var S: string);
  448. var
  449. len: integer;
  450. begin
  451. len := Length(S);
  452. if (len > 0) and ((S[1] <= ' ') or (S[len] <= ' ')) then
  453. S := Trim(S);
  454. end;
  455. function FetchToken(var Line: string; const Sep: string; bnTrim: boolean): string;
  456. var
  457. p: integer;
  458. begin
  459. p := Pos(Sep, Line);
  460. if (p > 0) then
  461. begin
  462. // give part until separator:
  463. if bnTrim then
  464. begin
  465. Result := TrimCopy(Line, 1, p - 1);
  466. Delete(Line, 1, p + Length(Sep) - 1);
  467. DoTrim(Line);
  468. end else
  469. begin
  470. Result := Copy(Line, 1, p - 1);
  471. Delete(Line, 1, p + Length(Sep) - 1);
  472. end;
  473. end else
  474. begin
  475. // give all rest:
  476. Result := Line;
  477. Line := '';
  478. if bnTrim then
  479. DoTrim(Result);
  480. end;
  481. end;
  482. procedure AdjustHeaderLine(var Line: string);
  483. var
  484. p, len: integer;
  485. Name: string;
  486. begin
  487. // Right-trim:
  488. len := Length(Line);
  489. if (len = 0) then
  490. Exit;
  491. if (Line[1] <= ' ') then
  492. Line := Trim(Line)
  493. else
  494. if (Line[len] <= ' ') then
  495. Line := TrimRight(Line);
  496. // Normalize arround ":"...
  497. p := Pos(':', Line);
  498. if (p > 1) and (p < Length(Line) - 1) then
  499. if (Line[p - 1] <= ' ') or not (Line[p + 1] <= ' ') or (Line[p + 2] <= ' ') then
  500. begin
  501. // Needs normalize...
  502. Name := FetchToken(Line, ':', True);
  503. //
  504. Line := Name + ': ' + Line;
  505. end;
  506. end;
  507. // for parsing: remove first Name="Value", separators either ";" or ","
  508. // Value may be quoted, but does not need to be quoted
  509. // Name may be missing (if no "=" is found, whole is Value)
  510. function FetchDequoted(var Line: string; out Name, Value: string): boolean;
  511. var
  512. len, startname, lenname, startvalue, lenvalue, Skip, rest, p: integer;
  513. bnName, bnSlash: boolean;
  514. begin
  515. len := Length(Line);
  516. // LTrim name:
  517. startname := 1;
  518. while (startname <= len) and (Line[startname] <= ' ') do
  519. Inc(startname);
  520. startvalue := startname;
  521. //
  522. if (startname > len) then
  523. begin
  524. // Line was empty (or blank)...
  525. Line := '';
  526. Name := '';
  527. Value := '';
  528. Result := False;
  529. exit;
  530. end;
  531. //
  532. // Seek end of name:
  533. bnName := False;
  534. lenname := 0;
  535. lenvalue := 0;
  536. while (startname + lenname <= len) do
  537. begin
  538. case Line[startname + lenname] of
  539. ';', ',', '"': break;
  540. '=':
  541. begin
  542. // End of name:
  543. startvalue := startname + lenname + 1;
  544. bnName := True;
  545. break;
  546. end;
  547. end;
  548. Inc(lenname);
  549. end;
  550. if not bnName then
  551. begin
  552. // no name...
  553. //startvalue:=startname; // already...
  554. lenvalue := lenname;
  555. lenname := 0;
  556. end;
  557. Name := TrimCopy(Line, startname, lenname);
  558. //
  559. Skip := 0;
  560. bnSlash := False;
  561. if (lenvalue = 0) then
  562. begin
  563. // ltrim:
  564. while (startvalue <= len) and (Line[startvalue] <= ' ') do
  565. Inc(startvalue);
  566. lenvalue := 0;
  567. if (Line[startvalue] = '"') then
  568. begin
  569. // quoted:
  570. Inc(startvalue);
  571. lenvalue := 0;
  572. while (startvalue + lenvalue <= len) do
  573. begin
  574. case Line[startvalue + lenvalue] of
  575. '\':
  576. begin
  577. bnSlash := True;
  578. Inc(lenvalue);
  579. end;
  580. '"':
  581. begin
  582. // end-quote...
  583. Skip := 1;
  584. break;
  585. end;
  586. end;
  587. Inc(lenvalue);
  588. end;
  589. end else
  590. while (startvalue + lenvalue <= len) do
  591. begin
  592. case Line[startvalue + lenvalue] of
  593. ';', ',': break;
  594. end;
  595. Inc(lenvalue);
  596. end// separated:
  597. ;
  598. end;
  599. Value := TrimCopy(Line, startvalue, lenvalue);
  600. //
  601. rest := startvalue + lenvalue + Skip;
  602. while (rest <= len) and (Line[rest] <= ' ') do
  603. Inc(rest);
  604. if (rest <= len) and (CharInSet(Line[rest], [';', ','])) then
  605. Inc(rest);
  606. Line := TrimCopy(Line, rest, Length(Line) - rest + 1);
  607. //
  608. if bnSlash then
  609. begin
  610. // Remove middle quoting markup:
  611. len := Length(Value);
  612. p := 1;
  613. while (p <= len) do
  614. begin
  615. if (Value[p] = '\') then
  616. begin
  617. Delete(Value, p, 1);
  618. Dec(len);
  619. end;
  620. Inc(p);
  621. end;
  622. end;
  623. //
  624. Result := True;
  625. end;
  626. function GetHeaderSubValue(Header: string; const Name: string): string;
  627. var
  628. S: string;
  629. begin
  630. Result := '';
  631. while (Header <> '') do
  632. begin
  633. FetchDequoted(Header, S, Result);
  634. if SameText(S, Name) then
  635. break;//exit;
  636. Result := '';
  637. end;
  638. end;
  639. procedure ReplaceHeaderSubValue(var Header: string; const Name, Value: string);
  640. var
  641. Parts: TStringList;
  642. S, S2: string;
  643. ls2: integer;
  644. begin
  645. // find existing Name="Value", value may be quoted and may be not quoted, Name= may occur inside other quoted value so may not use simple Pos()...
  646. Parts := TStringList.Create;
  647. try
  648. S2 := Name + '=';
  649. ls2 := Length(S2);
  650. //
  651. while (Header <> '') do
  652. begin
  653. S := Trim(FetchQSepValue(Header, ';'));
  654. //
  655. if (S <> '') and (ls2 >= Length(S)) and (S[ls2] = '=') and SameHead(S, S2)
  656. //and SameText(Copy(S,1,ls2),S2)
  657. then
  658. begin
  659. // Replace this:
  660. S := S2 + QuoteValue(Value);
  661. ls2 := 0;
  662. end;
  663. //
  664. Parts.Add(S);
  665. end;
  666. //
  667. if (ls2 > 0) then
  668. Parts.Add(S2 + QuoteValue(Value))// was not found...
  669. ;
  670. //
  671. // Combine into string:
  672. Header := CombineStrings(Parts, '; ');
  673. //
  674. finally
  675. Parts.Free;
  676. end;
  677. end;
  678. function CombineStrings(Strings: TStrings; const Separator: string): string;
  679. var
  680. S: string;
  681. i: integer;
  682. begin
  683. Result := '';
  684. for i := 0 to Strings.Count - 1 do
  685. begin
  686. S := Strings[i];
  687. if (i > 0) then
  688. Result := Result + Separator + S
  689. else
  690. Result := Result + S;
  691. end;
  692. end;
  693. function SameHead(const Str, SHead: string): boolean;
  694. begin
  695. Result := SameText(Copy(Str, 1, Length(SHead)), SHead);
  696. end;
  697. const
  698. // SysUtils.ShortDayNames may be translated with resources... here use constants:
  699. UsShortDayNames: array[1..7] of string = ('Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat');
  700. UsShortMonthNames: array[1..12] of string =
  701. ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec');
  702. function FormatHttpDate(LocalDate: TDateTime; bnIsLocal: boolean): string;
  703. var
  704. UtcDate: TDateTime;
  705. d, m, y, h, n, s, z: word;
  706. begin
  707. if (LocalDate <= 1) then
  708. begin
  709. Result := '';
  710. exit;
  711. end;
  712. // This format is recomended by RFC2616. it MUST be in GMT time-zone...
  713. // Sun, 06 Nov 1994 08:49:37 GMT ; RFC 822, updated by RFC 1123
  714. if bnIsLocal then
  715. UtcDate := LocalToUtcDateTime(LocalDate)
  716. else
  717. UtcDate := LocalDate;
  718. DecodeDate(UtcDate, y, m, d);
  719. DecodeTime(UtcDate, h, n, s, z);
  720. Result := Format('%s, %.2d %s %.4d %.2d:%.2d:%.2d GMT', [UsShortDayNames[DayOfWeek(UtcDate)],
  721. d, UsShortMonthNames[m], y, h, n, s]);
  722. end;
  723. function LocalToUtcDateTime(LocalDate: TDateTime): TDateTime;
  724. begin
  725. // UTC = local_time + bias
  726. if (LocalDate <> 0) then
  727. Result := LocalDate + TimeZoneBiasTime()
  728. else
  729. Result := 0;
  730. end;
  731. function UtcToLocalDateTime(UtcDate: TDateTime): TDateTime;
  732. begin
  733. // local_time = UTC - bias
  734. if (UtcDate <> 0) then
  735. Result := UtcDate - TimeZoneBiasTime()
  736. else
  737. Result := 0;
  738. end;
  739. const
  740. cMinuteToDateTime = 1 / (24 * 60);
  741. {$undef WIN32FILETIME}
  742. {$undef WIN32TZ}
  743. {$ifdef MSWINDOWS} {$ifndef CIL}
  744. {$define WIN32TZ}
  745. function TimeZoneBiasTime: TDateTime;
  746. var
  747. tzi: TTimeZoneInformation;
  748. Bias: integer;
  749. begin
  750. case GetTimeZoneInformation(tzi) of
  751. TIME_ZONE_ID_UNKNOWN: Bias := tzi.Bias;
  752. TIME_ZONE_ID_STANDARD: Bias := tzi.Bias + tzi.StandardBias;
  753. TIME_ZONE_ID_DAYLIGHT: Bias := tzi.Bias + tzi.DaylightBias;
  754. else
  755. Bias := 0;
  756. end;
  757. if (Bias <> 0) then
  758. Result := Bias * cMinuteToDateTime
  759. else
  760. Result := 0;
  761. end;
  762. {$define WIN32FILETIME}
  763. function FileTimeToUtcDateTime(const FileTime: TFileTime): TDateTime;
  764. var
  765. Sys: TSystemTime;
  766. begin
  767. if FileTimeToSystemTime(FileTime, Sys) then
  768. Result := EncodeDate(Sys.wYear, Sys.wMonth, Sys.wDay) + EncodeTime(Sys.wHour, Sys.wMinute,
  769. Sys.wSecond, Sys.wMilliseconds)
  770. else
  771. Result := 0;
  772. end;
  773. {$endif}{$endif}
  774. //
  775. {$ifndef WIN32TZ} // fallback for dotnet & linux:
  776. //const
  777. // cMinuteToDateTime=1/(24*60);
  778. function TimeZoneBiasTime: TDateTime;
  779. begin
  780. Result := SynaUtil.TimeZoneBias*cMinuteToDateTime;
  781. end;
  782. {$endif}
  783. function GetFileDateUtc(const FileName: string): TDateTime;
  784. var
  785. SR: TSearchRec;
  786. begin
  787. // This could work on linux also?
  788. if (FindFirst(FileName, faAnyFile, SR) = 0) then
  789. begin
  790. FindClose(SR);
  791. //
  792. {$ifdef WIN32FILETIME}// WIN32
  793. // Here we have directly UTC date-time:
  794. Result := FileTimeToUtcDateTime(SR.FindData.ftLastWriteTime);
  795. {$else ->fallback}
  796. Result:=LocalToUtcDateTime(FileDateToDateTime(SR.Time));
  797. {$endif}
  798. end else
  799. Result := 0;
  800. end;
  801. function ParseShortMonthName(const Token: string): integer;
  802. var
  803. i: integer;
  804. begin
  805. for i := 1 to 12 do
  806. if SameText(Token, UsShortMonthNames[i]) then
  807. begin
  808. Result := i;
  809. exit;
  810. end;
  811. Result := 0;
  812. end;
  813. function ParseHttpDate(Str: string; out DateTime: TDateTime): boolean;
  814. var
  815. Token: string;
  816. Int, y, m, d, h, n, s, tzh, tzm, tokencount: integer;
  817. TzOffset: double;
  818. begin
  819. DateTime := 0;
  820. // This format is recomended by RFC2616. it MUST be in GMT time-zone...
  821. // Sun, 06 Nov 1994 08:49:37 GMT ; RFC 822, updated by RFC 1123
  822. // These formats are also possible:
  823. // Sunday, 06-Nov-94 08:49:37 GMT ; RFC 850, obsoleted by RFC 1036
  824. // Sun Nov 6 08:49:37 1994 ; ANSI C's asctime() format
  825. // Anyhow due to robustness we will parse +0000 and -0000 timezones also...
  826. y := 0;
  827. m := 0;
  828. d := 0;
  829. h := 0;
  830. n := 0;
  831. s := 0;
  832. tokencount := 0;
  833. TzOffset := 0;
  834. while (Str <> '') do
  835. begin
  836. Token := FetchToken(Str, ' ', True);
  837. if (Token = '') then
  838. continue;
  839. //
  840. Inc(tokencount);
  841. if (tokencount > 31) then
  842. break;
  843. //
  844. Int := -1;
  845. if (CharInSet(Token[1], ['0'..'9'])) then
  846. Int := StrToIntDef(Token, -1);
  847. //
  848. case Length(Token) of
  849. 1, 2: if (d = 0) and (Int > 0) then
  850. d := Int;// Day...
  851. 3: if (m = 0) and (Int < 0) then
  852. m := ParseShortMonthName(Token);// Sun, GMT, Nov
  853. 4: if (y = 0) and (Int >= 1900) and (Int <= 2200) then
  854. y := Int;// 1999
  855. 5: if (CharInSet(Token[1], ['-', '+'])) and (CharInSet(Token[2], ['0'..'2'])) then
  856. begin
  857. // +0200, -0200
  858. tzh := StrToIntDef(Copy(Token, 2, 2), -1);
  859. tzm := StrToIntDef(Copy(Token, 4, 2), -1);
  860. if (tzh >= 0) and (tzm >= 0) then
  861. begin
  862. TzOffset := (tzh * (1 / 24)) + (tzm * (1 / (24 * 60)));
  863. if (Token[1] = '+') then
  864. TzOffset := -TzOffset;
  865. end;
  866. end;
  867. else
  868. if (Pos(':', Token) > 0) then
  869. begin
  870. // Time...
  871. h := StrToIntDef(FetchToken(Token, ':', True), 0);
  872. n := StrToIntDef(FetchToken(Token, ':', True), 0);
  873. s := StrToIntDef(FetchToken(Token, ':', True), 0);
  874. end else
  875. if (d = 0) and (Pos('-', Token) > 0) then
  876. begin
  877. // 06-Nov-94
  878. d := StrToIntDef(FetchToken(Token, '-', True), 0);
  879. m := ParseShortMonthName(FetchToken(Token, '-', True));
  880. if (m <> 0) then
  881. begin
  882. y := StrToIntDef(Token, -1);
  883. if (y >= 0) then
  884. if (y > 50) then
  885. Inc(y, 1900)
  886. else
  887. Inc(y, 2000);
  888. end;
  889. end;
  890. end;
  891. end;
  892. //
  893. if (m > 0) and (m <= 12) and (y >= 1900) and (d > 0) and (d <= MonthDays[IsLeapYear(y), m]) then
  894. begin
  895. // Valid date...
  896. DateTime := EncodeDate(y, m, d);
  897. // Check time:
  898. if (h >= 0) and (h <= 23) and (n >= 0) and (n <= 59) and (s >= 0) and (s <= 59) then
  899. DateTime := DateTime + EncodeTime(h, n, s, 0) + TzOffset;
  900. Result := True;
  901. end else
  902. Result := False;
  903. end;
  904. {$ifdef MSWINDOWS} {$ifndef CIL} {$define LOCALUTF} {$endif}{$endif}
  905. {$ifdef LOCALUTF}
  906. //For compatibility with Delphi5, use our and kernel functions...
  907. //U+00000000 - U+0000007F 0xxxxxxx
  908. //U+00000080 - U+000007FF 110xxxxx 10xxxxxx
  909. //U+00000800 - U+0000FFFF 1110xxxx 10xxxxxx 10xxxxxx
  910. //U+00010000 - U+001FFFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
  911. //U+00200000 - U+03FFFFFF 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
  912. //U+04000000 - U+7FFFFFFF 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
  913. function GetUtfCharLen(pc: PChar): integer;
  914. var
  915. b: byte;
  916. begin
  917. b := Ord(pc[0]);
  918. case b and $C0 of
  919. 0, $40: Result := 1;
  920. $C0: case b and $30 of
  921. $00, $10: if (Ord(pc[1]) and $C0 = $80) then
  922. Result := 2
  923. else
  924. Result := 0;// 2 bytes:
  925. $20: if (Ord(pc[1]) and $C0 = $80) and (Ord(pc[2]) and $C0 = $80) then
  926. Result := 3
  927. else
  928. Result := 0;// 3 bytes:
  929. else
  930. Result := 0;
  931. // Longer than UCS-2 (unicode >$FFFF) not supported...
  932. end;// Start multi-byte:
  933. else
  934. Result := 0; // illegal
  935. end;
  936. end;
  937. function IsUtf8(pc: PChar): boolean;
  938. var
  939. bn80: boolean;
  940. Len: integer;
  941. begin
  942. bn80 := False;
  943. while (pc^ <> #0) do
  944. if (byte(pc^) and $80 <> 0) then
  945. begin
  946. bn80 := True;
  947. Len := GetUtfCharLen(pc);
  948. if (Len > 0) then
  949. Inc(pc, Len)
  950. else
  951. begin
  952. // illegal bytes...
  953. Result := False;
  954. exit;
  955. end;
  956. end else
  957. Inc(pc);
  958. Result := bn80;
  959. end;
  960. function FromUtf8ToWin(const S: string): string;
  961. var
  962. WideBuf: PWideChar;
  963. Len, WideLen: integer;
  964. begin
  965. Len := Length(S);
  966. WideBuf := AllocMem(Len * 2 + 16);
  967. try
  968. WideLen := MultiByteToWideChar(CP_UTF8, 0, Pointer(S), Len, WideBuf, Len);
  969. if (WideLen = 0) then
  970. begin
  971. Result := '';
  972. exit;
  973. end;
  974. SetString(Result, PChar(nil), WideLen);
  975. Len := WideCharToMultiByte(0, 0, WideBuf, WideLen, Pointer(Result), WideLen, '@', nil);
  976. if (Len < WideLen) then
  977. SetLength(Result, WideLen);
  978. finally
  979. FreeMem(WideBuf);
  980. end;
  981. end;
  982. procedure TryDecodeUtf8(var Url: string);
  983. begin
  984. if IsUtf8(PChar(Url)) then
  985. Url := FromUtf8ToWin(Url);
  986. end;
  987. {$else ->Delphi7+}
  988. // For Delphi7+ can use function in pascal System unit...
  989. procedure TryDecodeUtf8(var Url: string);
  990. var S: string;
  991. begin
  992. S:=UTF8ToString(RawByteString(Url)); // returns empty, if not valid Utf8...
  993. if (S<>'') then
  994. Url:=S;
  995. end;
  996. {$endif}
  997. function ValHex(const S: AnsiString; var Value: integer): boolean;
  998. var
  999. code: integer;
  1000. begin
  1001. Val('$' + string(S), Value, Code);
  1002. Result := Code = 0;
  1003. end;
  1004. function AnsiCopy(const s: ansistring; StartIndex, Lenght: integer): ansistring;
  1005. begin
  1006. SetLength(Result, Lenght);
  1007. Move(s[StartIndex], Result[1], Lenght);
  1008. end;
  1009. function ConvertUrlChars(Url: string): string;
  1010. var
  1011. p, len, code: integer;
  1012. bnUtf: boolean;
  1013. buff: ansistring;
  1014. begin
  1015. // convert 'Documents%20and%20Settings' to 'Documents and Settings'
  1016. // and A+B to A B
  1017. Url := Url.Replace('+', ' ');
  1018. p := Pos('%', Url);
  1019. if (p = 0) then
  1020. Exit(Url);
  1021. //
  1022. // Exit(TIdURI.URLDecode(Url));
  1023. buff := ansistring(Url);
  1024. bnUtf := False;
  1025. len := Length(buff);
  1026. while (p <= len) do
  1027. begin
  1028. if (buff[p] = '%') then
  1029. if ValHex(AnsiCopy(buff, p + 1, 2), code) then
  1030. begin
  1031. Delete(buff, p + 1, 2);
  1032. Dec(len, 2);
  1033. buff[p] := ansichar(code);
  1034. if (code > $80) then
  1035. bnUtf := True;
  1036. end;
  1037. Inc(p);
  1038. end;
  1039. //
  1040. if bnUtf then
  1041. Result := UTF8ToString(RawByteString(buff))
  1042. else
  1043. Result := string(buff);
  1044. end;
  1045. { THeaderList }
  1046. function THeaderList.Add(const S: string): integer;
  1047. var
  1048. Index: integer;
  1049. Line: string;
  1050. begin
  1051. // No empty lines:
  1052. Line := S;
  1053. if (Line = '') then
  1054. Exit(-1);
  1055. // Check multi-line headers:
  1056. if (Line[1] <= ' ') then
  1057. begin
  1058. Index := Count - 1;
  1059. if (Index >= 0) then
  1060. begin
  1061. // Append to last line:
  1062. Strings[Index] := Strings[Index] + #13#10 + Line; // line includes leading blank...
  1063. Exit(Index);
  1064. end;
  1065. end;
  1066. // Common adjustment (trim and normalize arround ":")
  1067. AdjustHeaderLine(Line);
  1068. //
  1069. Result := inherited Add(Line);
  1070. end;
  1071. procedure THeaderList.Insert(Index: integer; const S: string);
  1072. var
  1073. S2: string;
  1074. begin
  1075. S2 := S;
  1076. if (S2 <> '') then
  1077. begin
  1078. // Common adjustment (trim and normalize arround ":")
  1079. AdjustHeaderLine(S2);
  1080. //
  1081. inherited Insert(Index, S2);
  1082. end;
  1083. end;
  1084. procedure THeaderList.Put(Index: integer; const S: string);
  1085. var
  1086. S2: string;
  1087. begin
  1088. S2 := S;
  1089. if (S2 <> '') then
  1090. begin
  1091. // Common adjustment (trim and normalize arround ":")
  1092. AdjustHeaderLine(S2);
  1093. //
  1094. inherited Put(Index, S2);
  1095. end;
  1096. end;
  1097. procedure THeaderList.AddValue(const Name, Value: string);
  1098. var
  1099. S: string;
  1100. begin
  1101. if (Name <> '') and (Value <> '') then
  1102. begin
  1103. S := Name + ': ' + Value;
  1104. AdjustHeaderLine(S);
  1105. inherited Add(S);
  1106. end;
  1107. end;
  1108. function THeaderList.IndexOfName(const Name: string): integer;
  1109. var
  1110. i, len: integer;
  1111. S: string;
  1112. begin
  1113. Result := -1;
  1114. len := Length(Name);
  1115. if (len > 0) then
  1116. for i := 0 to Count - 1 do
  1117. begin
  1118. S := Strings[i];
  1119. if (Length(S) > len) and (S[len + 1] = ':') and SameHead(S, Name) //and SameText(Copy(S,1,len),Name)
  1120. then
  1121. begin
  1122. Result := i;
  1123. break;//exit;
  1124. end;
  1125. end;
  1126. end;
  1127. function IsName(const Line, Name: string): boolean;
  1128. var
  1129. len: integer;
  1130. begin
  1131. len := Length(Name);
  1132. if (len > 0) and (Length(Line) > len) and (Line[len + 1] = ':') and SameHead(Line, Name)
  1133. //and SameText(Copy(Line,1,len),Name)
  1134. then
  1135. Result := True
  1136. else
  1137. Result := False;
  1138. end;
  1139. procedure LStrDel(var S: string; Index, Count: integer);
  1140. begin
  1141. Delete(S, Index, Count);
  1142. end;
  1143. // returns pos after quote...
  1144. function StrSkipQuoted(const S: string; pquote: integer): integer;
  1145. var
  1146. Q: char;
  1147. p, len: integer;
  1148. begin
  1149. p := pquote;
  1150. Q := S[p];
  1151. Inc(p);
  1152. len := Length(S);
  1153. while (p <= len) do
  1154. if (S[p] = Q) then
  1155. begin
  1156. // Found...
  1157. Inc(p);
  1158. Exit(p);
  1159. end else
  1160. if (S[p] = '\') then
  1161. Inc(p, 2)
  1162. else
  1163. Inc(p);
  1164. Result := 0;
  1165. end;
  1166. // seek next occurence after this pos:
  1167. function StrSeek(const S: string; C: char; StartPos: integer): integer;
  1168. var
  1169. p, len: integer;
  1170. begin
  1171. len := Length(S);
  1172. p := StartPos;
  1173. if (p <= 0) then
  1174. p := 1;
  1175. while (p <= len) do
  1176. begin
  1177. if (S[p] = C) then
  1178. Exit(p);
  1179. Inc(p);
  1180. end;
  1181. Result := len + 1;
  1182. end;
  1183. // remove first comma-separated value
  1184. function FetchQSepValue(var Line: string; const Sep: string): string;
  1185. var
  1186. pcomma, pquote, p, len: integer;
  1187. begin
  1188. // values are separated by "," but there may be another such in quotes...
  1189. pcomma := Pos(Sep, Line);
  1190. if (pcomma = 0) then
  1191. begin
  1192. // whole line is last part:
  1193. Result := Trim(Line);
  1194. Line := '';
  1195. Exit;
  1196. end;
  1197. // skip quoted content:
  1198. pquote := Pos('"', Line);
  1199. while (pquote > 0) and (pquote < pcomma) do
  1200. begin
  1201. // May be quoted, may have multiple quoted parts...
  1202. p := StrSkipQuoted(Line, pquote);
  1203. pquote := StrSeek(Line, '"', p);
  1204. pcomma := StrSeek(Line, Sep[1], p);
  1205. if (pcomma = 0) then
  1206. begin
  1207. // whole line is last part:
  1208. Result := Trim(Line);
  1209. Line := '';
  1210. exit;
  1211. end;
  1212. end;
  1213. // Extract part:
  1214. Result := TrimCopy(Line, 1, pcomma - 1);
  1215. // Remove part, comma and spaces:
  1216. len := Length(Line);
  1217. p := pcomma;
  1218. while (p < len) and ((Line[p + 1] <= ' ') or (Line[p + 1] = Sep[1])) do
  1219. Inc(p);
  1220. Delete(Line, 1, p);
  1221. end;
  1222. // according to RFC2616, comma-separated headers may be also duplicated...
  1223. procedure THeaderList.EnumHeaders(const Name: string; const Enum: THeaderEnum; const Sep: char;
  1224. LParam: NativeUInt = 0);
  1225. var
  1226. i, Index, Cnt: integer;
  1227. Line, Value: string;
  1228. begin
  1229. Index := IndexOfName(Name);
  1230. if (Index >= 0) then
  1231. begin
  1232. i := Index;
  1233. Line := Strings[i];
  1234. while True do
  1235. begin
  1236. // Process this line:
  1237. LStrDel(Line, 1, Length(Name) + 2); // remove 'Name: '
  1238. Line := Trim(Line);
  1239. //
  1240. while (Line <> '') do
  1241. begin
  1242. Value := FetchQSepValue(Line, Sep);
  1243. if (Value <> '') then
  1244. if Enum(Value, LParam) then
  1245. Exit;
  1246. end;
  1247. // find next...
  1248. Inc(i);
  1249. Cnt := Count;
  1250. while (i < Cnt) do
  1251. begin
  1252. Line := Strings[i];
  1253. if IsName(Line, Name) then
  1254. break;
  1255. Inc(i);
  1256. end;
  1257. if (i >= Cnt) then
  1258. break;
  1259. end;
  1260. end;
  1261. end;
  1262. {$ifndef CIL}
  1263. // Simple pascal:
  1264. type
  1265. PHeaderFinder = ^THeaderFinder;
  1266. THeaderFinder = record
  1267. FindValue: PString;
  1268. Found: boolean;
  1269. end;
  1270. {$else ->dotnet is more complicated}
  1271. type
  1272. THeaderFinder=class(TObject)
  1273. public
  1274. FindValue: string;
  1275. Found: Boolean;
  1276. function CheckHttpFindValue(const Value: string; LParam: Longint): Boolean;
  1277. end;
  1278. PHeaderFinder=THeaderFinder;
  1279. function THeaderFinder.CheckHttpFindValue(const Value: string; LParam: Longint): Boolean;
  1280. var S: string;
  1281. begin
  1282. S:=Value;
  1283. if SameText(FetchToken(S,'=',True),FindValue) then begin
  1284. Found:=True;
  1285. Result:=True; // stop.
  1286. end else
  1287. Result:=False; // continue...
  1288. end;
  1289. {$endif}
  1290. function THeaderList.CheckHttpFindValue(const Value: string; LParam: NativeUInt): boolean;
  1291. {$ifndef CIL}
  1292. var
  1293. S: string;
  1294. Finder: PHeaderFinder;
  1295. {$endif}
  1296. begin
  1297. {$ifndef CIL}
  1298. Finder := PHeaderFinder(LParam);
  1299. S := Value;
  1300. if SameText(FetchToken(S, '=', True), Finder.FindValue^) then
  1301. begin
  1302. Finder.Found := True;
  1303. Result := True; // stop.
  1304. end else
  1305. {$endif}
  1306. Result := False; // continue...
  1307. end;
  1308. function THeaderList.HasValue(const Name, Value: string): boolean;
  1309. var
  1310. Finder: THeaderFinder;
  1311. begin
  1312. {$ifndef CIL}
  1313. // Simple:
  1314. Finder.FindValue := @Value;
  1315. Finder.Found := False;
  1316. //
  1317. EnumHeaders(Name, Self.CheckHttpFindValue, ',', NativeUInt(@Finder));
  1318. Result := Finder.Found;
  1319. //
  1320. {$else ->dotnet, little more complicated}
  1321. //
  1322. Finder:=THeaderFinder.Create;
  1323. Finder.FindValue:=Value;
  1324. Finder.Found:=False;
  1325. EnumHeaders(Name,Finder.CheckHttpFindValue,0);
  1326. Result:=Finder.Found;
  1327. Finder.Free;
  1328. {$endif}
  1329. end;
  1330. function THeaderList.GetValueByName(const Name: string): string;
  1331. var
  1332. Index, p: integer;
  1333. begin
  1334. Index := IndexOfName(Name);
  1335. if (Index >= 0) then
  1336. begin
  1337. //Result:=GetValueByIndex(Index);
  1338. Result := Strings[Index];
  1339. //System.Delete(Result,Length(Name)+2); // remove 'Name: '
  1340. p := Length(Name) + 2;
  1341. Result := Copy(Result, p + 1, Length(Result) - p);
  1342. end else
  1343. Result := '';
  1344. end;
  1345. procedure THeaderList.SetValueByName(const Name, Value: string);
  1346. var
  1347. Index: integer;
  1348. S: string;
  1349. begin
  1350. if (Name <> '') then
  1351. if (Value <> '') then
  1352. begin
  1353. Index := IndexOfName(Name);
  1354. S := Trim(Name) + ': ' + Trim(Value);
  1355. if (Index >= 0) then
  1356. inherited Put(Index, S) //Strings[Index]:=S
  1357. else
  1358. inherited Add(S);
  1359. end else
  1360. RemoveValue(Name);
  1361. end;
  1362. function THeaderList.RemoveValue(const Name: string): boolean;
  1363. var
  1364. Index, Count: integer;
  1365. begin
  1366. Result := False;
  1367. Index := IndexOfName(Name);
  1368. if (Index >= 0) then
  1369. begin
  1370. Delete(Index);
  1371. Result := True;
  1372. //
  1373. // Remove all occurences:
  1374. Count := Self.Count;
  1375. while (Index < Count) do
  1376. if IsName(Strings[Index], Name) then
  1377. begin
  1378. Delete(Index);
  1379. Dec(Count);
  1380. end else
  1381. Inc(Index);
  1382. end;
  1383. end;
  1384. function THeaderList.GetNameByIndex(Index: integer): string;
  1385. var
  1386. p: integer;
  1387. begin
  1388. Result := Strings[Index];
  1389. p := Pos(':', Result);
  1390. if (p > 0) then
  1391. Result := Copy(Result, 1, p - 1)
  1392. else
  1393. Result := '';
  1394. end;
  1395. function THeaderList.GetValueByIndex(Index: integer): string;
  1396. var
  1397. p: integer;
  1398. begin
  1399. Result := Strings[Index];
  1400. p := Pos(':', Result);
  1401. if (p > 0) then
  1402. begin
  1403. Inc(p); // remove space after colon also...
  1404. Result := TrimCopy(Result, p + 1, Length(Result) - p);
  1405. end;
  1406. end;
  1407. function THeaderList.GetSubValue(const Name, SubName: string): string;
  1408. begin
  1409. Result := Values[Name];
  1410. if (Result <> '') then
  1411. Result := GetHeaderSubValue(Result, SubName);
  1412. end;
  1413. procedure THeaderList.SetSubValue(const Name, SubName, Value: string);
  1414. var
  1415. S: string;
  1416. Index: integer;
  1417. begin
  1418. Index := IndexOfName(Name);
  1419. if (Index >= 0) then
  1420. S := ValuesByIndex[Index]//Values[Name];
  1421. else
  1422. S := '';
  1423. //
  1424. if (S <> '') then
  1425. ReplaceHeaderSubValue(S, SubName, Value)// Replace in existing header:
  1426. else
  1427. S := Format('%s=%s', [SubName, QuoteValue(Value)]);
  1428. //
  1429. S := Trim(Name) + ': ' + Trim(S);
  1430. //
  1431. if (Index >= 0) then
  1432. inherited Put(Index, S)
  1433. else
  1434. inherited Add(S);
  1435. end;
  1436. { THttpRequest }
  1437. constructor THttpRequest.Create;
  1438. begin
  1439. inherited Create;
  1440. FHeaders := THeaderList.Create;
  1441. FParams := TStringList.Create;
  1442. FCookies := THttpCookies.Create;
  1443. end;
  1444. destructor THttpRequest.Destroy;
  1445. begin
  1446. FreeAndNil({FContentStream}FPostStream);
  1447. FreeAndNil(FHeaders);
  1448. FreeAndNil(FParams);
  1449. FreeAndNil(FCookies);
  1450. inherited;
  1451. end;
  1452. procedure THttpRequest.Assign(Source: TPersistent);
  1453. var
  1454. Req: THttpRequest;
  1455. Lines: TStrings;
  1456. Temp2: TStringList;
  1457. i, Count: integer;
  1458. S: string;
  1459. begin
  1460. if (Source is THttpRequest) then
  1461. begin
  1462. Req := THttpRequest(Source);
  1463. FHeaders.Assign(Req.FHeaders);
  1464. FCookies.Assign(Req.FCookies);
  1465. FUrl := Req.FUrl;
  1466. FMethod := Req.FMethod;
  1467. FProtocol := Req.FProtocol;
  1468. FContent := Req.FContent;
  1469. //FContentStream := Req.FContentStream;
  1470. //Req.FContentStream := nil; // only 1 request may own the content stream...
  1471. FPostStream := Req.FPostStream;
  1472. Req.FPostStream := nil; // only 1 request may own the content stream...
  1473. FStatusCode := Req.FStatusCode;
  1474. FStatusMsg := Req.FStatusMsg;
  1475. FConnection := Req.FConnection;
  1476. FFlags := Req.FFlags;
  1477. end else
  1478. if (Source is TStrings) then
  1479. begin
  1480. Lines := TStrings(Source);
  1481. Temp2 := nil;
  1482. try
  1483. // Load headers:
  1484. Headers.Clear;
  1485. i := 0;
  1486. Count := Lines.Count;
  1487. while (i < Count) do
  1488. begin
  1489. S := Lines[i];
  1490. if (S = '') then
  1491. begin
  1492. // End of headers...
  1493. Inc(i);
  1494. break;
  1495. end;
  1496. Headers.Add(S);
  1497. Inc(i);
  1498. end;
  1499. //
  1500. if (i < Count) then
  1501. begin
  1502. // Load content:
  1503. // It is usually much faster to copy strings to new list than to delete items from start...
  1504. Temp2 := TStringList.Create;
  1505. Temp2.Capacity := Count - i;
  1506. while (i < Count) do
  1507. begin
  1508. Temp2.Add(Lines[i]);
  1509. Inc(i);
  1510. end;
  1511. FreeAndNil(Temp2);
  1512. FContent := Temp2.Text;
  1513. end;
  1514. finally
  1515. FreeAndNil(Temp2);
  1516. end;
  1517. end else
  1518. inherited;
  1519. end;
  1520. procedure THttpRequest.SetHeaders(Value: THeaderList);
  1521. begin
  1522. if (Value <> nil) then
  1523. FHeaders.Assign(Value)
  1524. else
  1525. FHeaders.Clear;
  1526. end;
  1527. procedure THttpRequest.SetCookies(Value: THttpCookies);
  1528. begin
  1529. if (Value <> nil) then
  1530. FCookies.Assign(Value)
  1531. else
  1532. FCookies.Clear;
  1533. end;
  1534. type
  1535. THttpStatusMsg = record
  1536. Code: integer;
  1537. Msg: string;
  1538. end;
  1539. const
  1540. // status codes defined in RFC2616:
  1541. HttpStatusMsgs: array[0..39] of THttpStatusMsg = (
  1542. // Common codes:
  1543. (Code: 200; Msg: 'OK'),
  1544. (Code: 403; Msg: 'Forbidden'),
  1545. (Code: 404; Msg: 'Not Found'),
  1546. (Code: 401; Msg: 'Unauthorized'),
  1547. (Code: 500; Msg: 'Internal Server Error'),
  1548. (Code: 302; Msg: 'Found'), // use this for redirection
  1549. (Code: 304; Msg: 'Not Modified'),
  1550. (Code: 206; Msg: 'Partial Content'),
  1551. //
  1552. (Code: 100; Msg: 'Continue'),
  1553. (Code: 101; Msg: 'Switching Protocols'),
  1554. (Code: 201; Msg: 'Created'),
  1555. (Code: 202; Msg: 'Accepted'),
  1556. (Code: 203; Msg: 'Non-Authoritative Information'),
  1557. (Code: 204; Msg: 'No Content'),
  1558. (Code: 205; Msg: 'Reset Content'),
  1559. (Code: 300; Msg: 'Multiple Choices'), //also possible for redirection...
  1560. (Code: 301; Msg: 'Moved Permanently'), //also possible for redirection...
  1561. (Code: 303; Msg: 'See Other'), //also possible for redirection...
  1562. (Code: 305; Msg: 'Use Proxy'), //also possible for redirection...
  1563. (Code: 307; Msg: 'Temporary Redirect'),//also possible for redirection...
  1564. (Code: 400; Msg: 'Bad Request'),
  1565. (Code: 402; Msg: 'Payment Required'),
  1566. (Code: 405; Msg: 'Method Not Allowed'),
  1567. (Code: 406; Msg: 'Not Acceptable'),
  1568. (Code: 407; Msg: 'Proxy Authentication Required'),
  1569. (Code: 408; Msg: 'Request Timeout'),
  1570. (Code: 409; Msg: 'Conflict'),
  1571. (Code: 410; Msg: 'Gone'),
  1572. (Code: 411; Msg: 'Length Required'),
  1573. (Code: 412; Msg: 'Precondition Failed'),
  1574. (Code: 413; Msg: 'Request Entity Too Large'),
  1575. (Code: 414; Msg: 'Request-URI Too Long'),
  1576. (Code: 415; Msg: 'Unsupported Media Type'),
  1577. (Code: 416; Msg: 'Requested Range Not Satisfiable'),
  1578. (Code: 417; Msg: 'Expectation Failed'),
  1579. (Code: 501; Msg: 'Not Implemented'),
  1580. (Code: 502; Msg: 'Bad Gateway'),
  1581. (Code: 503; Msg: 'Service Unavailable'),
  1582. (Code: 504; Msg: 'Gateway Timeout'),
  1583. (Code: 505; Msg: 'HTTP Version Not Supported')
  1584. );
  1585. procedure THttpRequest.SetStatusCode(Value: integer);
  1586. begin
  1587. FStatusCode := Value;
  1588. GetHttpStatusMsg(FStatusCode, FStatusMsg);
  1589. end;
  1590. function GetHttpStatusMsg(StatusCode: integer; var StatusMsg: string): boolean;
  1591. var
  1592. i: integer;
  1593. begin
  1594. for i := Low(HttpStatusMsgs) to High(HttpStatusMsgs) do
  1595. if (HttpStatusMsgs[i].Code = StatusCode) then
  1596. begin
  1597. StatusMsg := HttpStatusMsgs[i].Msg;
  1598. Result := True;
  1599. exit;
  1600. end;
  1601. Result := False;
  1602. end;
  1603. function THttpRequest.GetFlagBool(Index: integer): boolean;
  1604. var
  1605. Mask: integer;
  1606. begin
  1607. Mask := 1 shl Index;
  1608. Result := (FFlags and Mask <> 0);
  1609. end;
  1610. procedure THttpRequest.SetFlagBool(Index: integer; Value: boolean);
  1611. var
  1612. Mask: integer;
  1613. begin
  1614. Mask := 1 shl Index;
  1615. if Value then
  1616. FFlags := FFlags or Mask
  1617. else
  1618. FFlags := FFlags and not Mask;
  1619. end;
  1620. procedure THttpRequest.ApplyHeaders(bnIsServer: boolean);
  1621. var
  1622. S: string;
  1623. p: integer;
  1624. begin
  1625. if bnIsServer then
  1626. Cookies.LoadClientCookies(Headers)
  1627. else
  1628. Cookies.LoadServerCookies(Headers);
  1629. //
  1630. // Parse parameters in URL:
  1631. FParams.Clear;
  1632. p := Pos('?', Url);
  1633. if (p > 0) then
  1634. begin
  1635. S := Copy(Url, p + 1, Length(Url) - p);
  1636. while (S <> '') do
  1637. FParams.Add(ConvertUrlChars(Trim(FetchQSepValue(S, '&'))));
  1638. end;
  1639. end;
  1640. {Sample from RFC1867:
  1641. Content-type: multipart/form-data, boundary=AaB03x
  1642. --AaB03x
  1643. content-disposition: form-data; name="field1"
  1644. Joe Blow
  1645. --AaB03x
  1646. content-disposition: form-data; name="pics"; filename="file1.txt"
  1647. Content-Type: text/plain
  1648. ... contents of file1.txt ...
  1649. --AaB03x--
  1650. {}
  1651. procedure THttpRequest.ParsePostFormData;
  1652. var
  1653. S: string;
  1654. //p: integer;
  1655. begin
  1656. if Content.StartsWith('--') then
  1657. EnumMultiPart(Content, Boundary, AddMultiPartFormItem)
  1658. else
  1659. begin
  1660. S := Content;
  1661. while (S <> '') do
  1662. FParams.Add(ConvertUrlChars(Trim(FetchQSepValue(S, '&'))));
  1663. end;
  1664. end;
  1665. function THttpRequest.AddMultiPartFormItem(Headers: THeaderList; const FieldName, Content: string): boolean;
  1666. var
  1667. S: string;
  1668. begin
  1669. S := Headers.SubValues['Content-Disposition', 'filename'];
  1670. if (S <> '') then // will add FieldName=filename
  1671. else
  1672. S := Content// will add FieldName=Content
  1673. ;
  1674. //
  1675. if (FieldName <> '') then
  1676. FParams.Add(FieldName + '=' + S)
  1677. else
  1678. FParams.Add(S);
  1679. //
  1680. Result := False; // all...
  1681. end;
  1682. type
  1683. TGetPostParamInfo = class(TObject)
  1684. public
  1685. ParamName: string;
  1686. ParamData: string;
  1687. bnFound: boolean;
  1688. function FindParamEnum(Headers: THeaderList; const FieldName, Content: string): boolean;
  1689. end;
  1690. function TGetPostParamInfo.FindParamEnum(Headers: THeaderList; const FieldName, Content: string): boolean;
  1691. begin
  1692. if SameText(FieldName, ParamName) then
  1693. begin
  1694. ParamData := Content;
  1695. bnFound := True;
  1696. Result := True; // stop.
  1697. end else
  1698. Result := False; // continue...
  1699. end;
  1700. function THttpRequest.GetPostFormParam(const ParamName: string; var ParamData: string): boolean;
  1701. var
  1702. Info: TGetPostParamInfo;
  1703. begin
  1704. Info := TGetPostParamInfo.Create;
  1705. try
  1706. Info.ParamName := ParamName;
  1707. Info.ParamData := ParamData;
  1708. EnumMultiPart(FContent, Boundary, Info.FindParamEnum);
  1709. ParamData := Info.ParamData;
  1710. Result := Info.bnFound;
  1711. finally
  1712. Info.Free;
  1713. end;
  1714. end;
  1715. procedure EnumMultiPart(ContentData, Boundary: string; const Enum: TMultipartEnumCallback);
  1716. function FetchLine(var Rest: string): string;
  1717. begin
  1718. Result := FetchToken(Rest, #13#10, False);
  1719. end;
  1720. var
  1721. Line: string;
  1722. Headers: THeaderList;
  1723. p, lbound: integer;
  1724. bnTerm, bnPart: boolean;
  1725. begin
  1726. // cannot use TStringList, since it would damage binary parts (uploaded files)?!
  1727. // could consume leading part of ContentData, but it can be very slow on large uploads... well, dotnet is slow anyway...
  1728. //
  1729. if (Boundary = '') then
  1730. begin
  1731. // autodetect boundary:
  1732. while (ContentData <> '') do
  1733. begin
  1734. Line := FetchLine(ContentData);
  1735. if (Line <> '') and (Line[1] = '-') and (Line[2] = '-') then
  1736. begin
  1737. //Delete(Line,1,2); Boundary:='--'+Line;
  1738. Boundary := Line; // contains leading '--'
  1739. break;
  1740. end;
  1741. end;
  1742. end else
  1743. begin
  1744. // Seek leading boundary:
  1745. Insert('--', Boundary, 1);
  1746. while (ContentData <> '') do
  1747. begin
  1748. Line := FetchLine(ContentData);
  1749. if (Line = '') then
  1750. continue;
  1751. if (Line = Boundary) then
  1752. break;
  1753. if (Line = Boundary + '--') then
  1754. exit;
  1755. end;
  1756. end;
  1757. lbound := Length(Boundary);
  1758. //
  1759. Headers := THeaderList.Create;
  1760. try
  1761. while (ContentData <> '') do
  1762. begin
  1763. // Parse part headers:
  1764. Headers.Clear;
  1765. while (ContentData <> '') do
  1766. begin
  1767. Line := FetchLine(ContentData);
  1768. if (Line = '') then
  1769. break;
  1770. Headers.Add(Line);
  1771. end;
  1772. // Parse part body:
  1773. bnTerm := False;
  1774. bnPart := False;
  1775. p := 1;
  1776. while (p < Length(ContentData)) do
  1777. begin
  1778. if (ContentData[p] = #13) and (ContentData[p + 1] = #10) and (ContentData[p + 2] = '-') and
  1779. (ContentData[p + 3] = '-') and CharInSet(ContentData[p + 2 + lbound], [#13, '-']) and
  1780. CharInSet(ContentData[p + 3 + lbound], [#10, '-']) then
  1781. begin
  1782. Line := Copy(ContentData, p + 2, lbound);
  1783. if (Line = Boundary) then
  1784. begin
  1785. // End of part body here:
  1786. Line := Copy(ContentData, 1, p - 1);
  1787. Inc(p, 2); // skip #13#10
  1788. Inc(p, lbound); // skip '--Boundary'
  1789. bnTerm := (ContentData[p] = '-');
  1790. Inc(p, 2); // skip either #13#10 or '--'
  1791. Delete(ContentData, 1, p);
  1792. //
  1793. if Enum(Headers, Headers.SubValues['Content-Disposition', 'name'], Line) then
  1794. exit;
  1795. Line := '';
  1796. //
  1797. bnPart := True;
  1798. break;
  1799. end;
  1800. end;
  1801. Inc(p);
  1802. end;
  1803. //
  1804. if bnTerm then // final boundary reached...
  1805. break;
  1806. if not bnPart then // input was incomplete, no boundary after data was found...
  1807. break;
  1808. end;
  1809. finally
  1810. Headers.Free;
  1811. end;
  1812. end;
  1813. const
  1814. StrPropNames: array[0..19] of string = (
  1815. 'Content-Type', // 0
  1816. 'Content-Type', // 1
  1817. 'Content-Disposition', // 2
  1818. 'Content-Disposition', // 3
  1819. 'Location', // 4
  1820. 'Etag', // 5
  1821. 'Host', // 6
  1822. 'Referer', // 7
  1823. 'User-Agent', // 8
  1824. 'Vary', // 9
  1825. 'WWW-Authenticate', //10 //!!!TODO
  1826. 'Authorization', //11
  1827. 'Content-Type', //12
  1828. 'Content-Encoding', //13
  1829. 'Cache-control', //14
  1830. 'Pragma', //15
  1831. 'Server', //16
  1832. 'Accept-Encoding', //17
  1833. 'Content-Length', //18
  1834. 'Transfer-Encoding' //19
  1835. );
  1836. function THttpRequest.GetStrProp(Index: integer): string;
  1837. var
  1838. p: integer;
  1839. begin
  1840. Result := '';
  1841. if (Index >= 0) and (Index <= High(StrPropNames)) then
  1842. begin
  1843. Result := Headers[StrPropNames[Index]];
  1844. //
  1845. case Index of
  1846. 1:
  1847. begin
  1848. // BaseContentType... remove sub-type...
  1849. p := Pos(';', Result);
  1850. if (p > 0) then
  1851. Result := TrimCopy(Result, 1, p - 1);
  1852. end;
  1853. 3: Result := GetHeaderSubValue(Result, 'filename');// TargetFileName, extract it:
  1854. // Content-Disposition: attachment; filename="Filename" also works without the "attachment"...
  1855. 12: Result := GetHeaderSubValue(Result, 'boundary');// Boundary:
  1856. end;
  1857. end;
  1858. end;
  1859. procedure THttpRequest.SetStrProp(Index: integer; const Value: string);
  1860. var
  1861. i: int64;
  1862. begin
  1863. if (Index >= 0) and (Index <= High(StrPropNames)) then
  1864. case Index of
  1865. 3:
  1866. Headers.SubValues[StrPropNames[Index], 'filename'] := Value;// TargetFileName:
  1867. // Content-Disposition: attachment; filename="Filename" also works without the "attachment"...
  1868. 12:
  1869. begin
  1870. // Boundary:
  1871. if (Headers.Values[StrPropNames[Index]] = '') then
  1872. Headers.Values[StrPropNames[Index]] := 'multipart/mixed';
  1873. //
  1874. Headers.SubValues[StrPropNames[Index], 'boundary'] := Value;
  1875. end;
  1876. 18:
  1877. begin
  1878. i := 0;
  1879. if TryStrToInt64(Value, i) and (i > 0) then
  1880. Headers[StrPropNames[Index]] := Value;
  1881. end
  1882. else
  1883. Headers[StrPropNames[Index]] := Value;
  1884. end;
  1885. end;
  1886. const
  1887. DatePropNames: array[0..3] of string = (
  1888. 'Date',
  1889. 'Last-Modified',
  1890. 'Last-Modified',
  1891. 'Expires'
  1892. );
  1893. DatePropIsLocal: array[0..3] of boolean = (
  1894. True,
  1895. True,
  1896. False,
  1897. False
  1898. );
  1899. procedure THttpRequest.SetCharSet(const Value: string);
  1900. begin
  1901. FCharSet := Value;
  1902. Headers.SubValues['Content-Type', 'charset'] := Value;
  1903. end;
  1904. function THttpRequest.GetDateProp(Index: integer): TDateTime;
  1905. begin
  1906. if (Index >= 0) and (Index <= High(DatePropNames)) and ParseHttpDate(Headers[DatePropNames[Index]], Result) then
  1907. begin
  1908. if DatePropIsLocal[Index] then
  1909. Result := UtcToLocalDateTime(Result);
  1910. exit;
  1911. end;
  1912. //
  1913. Result := 0;
  1914. end;
  1915. procedure THttpRequest.SetDateProp(Index: integer; const Value: TDateTime);
  1916. var
  1917. bnIsLocal: boolean;
  1918. begin
  1919. if (Index >= 0) and (Index <= High(DatePropNames)) then
  1920. begin
  1921. bnIsLocal := DatePropIsLocal[Index];
  1922. Headers[DatePropNames[Index]] := FormatHttpDate(Value, bnIsLocal);
  1923. end;
  1924. end;
  1925. procedure THttpRequest.ServeFile(const LocalFileName: string);
  1926. begin
  1927. FreeAndNil({FContentStream}FPostStream);
  1928. //
  1929. if FileExists(LocalFileName) then
  1930. begin
  1931. //LastModified:=GetFileDateUtc(LocalFileName); // LastModified property is in LOCAL time, converting to UTC!
  1932. Headers[DatePropNames[1]{'Last-Modified'}] := FormatHttpDate(GetFileDateUtc(LocalFileName), False);
  1933. //
  1934. //FreeAndNil(FContentStream);
  1935. {ContentStream}PostStream := TFileStream.Create(LocalFileName, fmOpenRead or fmShareDenyWrite);
  1936. //
  1937. ContentType := DetectContentType(LocalFileName);
  1938. //
  1939. StatusCode := 200; // OK
  1940. //
  1941. end else
  1942. begin
  1943. // File not found:
  1944. StatusCode := 404; // Not Found
  1945. // Give some message:
  1946. if (Error404Url <> '') then
  1947. Redirect(Error404Url)
  1948. else
  1949. if (Error404DocText <> '') then
  1950. begin
  1951. Content := Error404DocText;
  1952. ContentType := 'text/html';
  1953. end else
  1954. begin
  1955. // Fallback:
  1956. Content := '404 - not found.';
  1957. ContentType := 'text/plain';
  1958. end;
  1959. end;
  1960. end;
  1961. function DetectContentType(const FileName: string): string;
  1962. var
  1963. Ext: string;
  1964. begin
  1965. // By file extension:
  1966. Ext := ExtractFileExt(FileName);
  1967. Result := GetContentTypeByExt(Ext);
  1968. //if (Result <> '') then
  1969. // Exit;
  1970. //
  1971. // Auto-detect by contents?
  1972. // Not here...
  1973. //Result := '';
  1974. end;
  1975. var
  1976. ContentTypes: TStringList;
  1977. procedure RegisterContentType(const Ext, ContentType: string);
  1978. var
  1979. S, Prev: string;
  1980. Index: integer;
  1981. begin
  1982. S := Ext + '=' + ContentType;
  1983. if (S[1] = '=') then
  1984. exit;
  1985. if (S[1] <> '.') then
  1986. Insert('.', S, 1);
  1987. //
  1988. //
  1989. Index := 0;
  1990. ContentTypes.Find(S, Index);
  1991. if (Index < ContentTypes.Count) then
  1992. begin
  1993. Prev := ContentTypes[Index];
  1994. if SameText(FetchToken(Prev, '=', True), Ext) then
  1995. begin
  1996. ContentTypes[Index] := S;
  1997. S := '';//exit;
  1998. end;
  1999. end;
  2000. if (S <> '') then
  2001. ContentTypes.Add(S);
  2002. end;
  2003. procedure RegisterInternalContentTypes;
  2004. begin
  2005. // register some basic content types...
  2006. // other get registered from configuration or from registry:
  2007. RegisterContentType('.htm', 'text/html');
  2008. RegisterContentType('.html', 'text/html');
  2009. RegisterContentType('.xml', 'text/xml');
  2010. RegisterContentType('.json', 'application/json');
  2011. RegisterContentType('.txt', 'text/plain');
  2012. RegisterContentType('.jpg', 'image/jpeg');
  2013. RegisterContentType('.gif', 'image/gif');
  2014. RegisterContentType('.png', 'image/png');
  2015. RegisterContentType('.css', 'text/css');
  2016. RegisterContentType('.ico', 'image/x-icon');
  2017. RegisterContentType('.bmp', 'image/bmp');
  2018. RegisterContentType('.htc', 'text/x-component');
  2019. RegisterContentType('.js', 'text/javascript');
  2020. RegisterContentType('.pdf', 'application/pdf');
  2021. end;
  2022. function GetContentTypeByExt(const Ext: string): string;
  2023. var
  2024. Index: integer;
  2025. begin
  2026. Result := '';
  2027. ContentTypes.Find(Ext + '=', Index);
  2028. if (Index < ContentTypes.Count) then
  2029. begin
  2030. Result := ContentTypes[Index];
  2031. if not SameText(FetchToken(Result, '=', True), Ext) then
  2032. Result := '';
  2033. end;
  2034. end;
  2035. {$ifdef MSWINDOWS}
  2036. // Win32 specific...
  2037. //[HKEY_CLASSES_ROOT\.xsl]
  2038. //@="xslfile"
  2039. //"Content Type"="text/xml"
  2040. procedure RegisterContentTypesFromRegistry;
  2041. var
  2042. Key, SubKey: HKEY;
  2043. KeyIndex: integer;
  2044. CbName, CbData: DWORD;
  2045. KeyName, Value: string;
  2046. begin
  2047. KeyIndex := 0;
  2048. Key := HKEY_CLASSES_ROOT;
  2049. CbName := 128;//x080922: 16;
  2050. SetLength(KeyName, CbName);
  2051. while (RegEnumKeyEx(Key, KeyIndex, @KeyName[1], CbName, nil, nil, nil, nil) = 0) do
  2052. begin
  2053. SetLength(KeyName, CbName);
  2054. if (KeyName <> '') and (KeyName[1] = '.') and (RegOpenKeyEx(Key, PChar(KeyName), 0, KEY_READ, SubKey) = 0) then
  2055. begin
  2056. CbData := 64;
  2057. SetLength(Value, CbData + 8);
  2058. if (RegQueryValueEx(SubKey, 'Content Type', nil, nil, @Value[1], @CbData) = 0) and (Value <> '') then
  2059. begin
  2060. SetLength(Value, CbData);
  2061. RegisterContentType(KeyName, Value);
  2062. end;
  2063. RegCloseKey(SubKey);
  2064. end;
  2065. //
  2066. Inc(KeyIndex);
  2067. CbName := 128;//x080922: 16;
  2068. SetLength(KeyName, CbName);
  2069. end;
  2070. end;
  2071. {$endif MSWINDOWS}
  2072. procedure THttpRequest.Redirect(const aUrl: string);
  2073. begin
  2074. //StatusCode := 302; // there are other 30x codes, but some HTTP/1.0 browsers do not understand them and understand only 302...
  2075. Self.Location := aUrl;
  2076. end;
  2077. // parse: 'GET /index.html HTTP/1.1' // used by server
  2078. procedure THttpRequest.ParseFirstRequestLine(Line: string);
  2079. var
  2080. p: integer;
  2081. begin
  2082. Self.FMethod := FetchToken(Line, ' ', True); // this trims Command...
  2083. p := Length(Line) - 7;
  2084. if (p > 0) and SameText(Copy(Line, p, 4), 'HTTP') then
  2085. begin
  2086. Self.FProtocol := Copy(Line, p, 8);
  2087. Self.FUrl := TrimCopy(Line, 1, p - 1);
  2088. end else
  2089. begin
  2090. Self.FUrl := FetchToken(Line, ' ', True);
  2091. Self.FProtocol := Line;
  2092. end;
  2093. p := Pos('?', Url) - 1;
  2094. if p > 0 then
  2095. FDocument := Copy(Url, 1, p)
  2096. else
  2097. FDocument := Url;
  2098. end;
  2099. // parse: 'HTTP/1.1 200 OK' // used by client
  2100. procedure THttpRequest.ParseFirstResponseLine(Line: string);
  2101. begin
  2102. FProtocol := FetchToken(Line, ' ', True);
  2103. FStatusCode := StrToIntDef(FetchToken(Line, ' ', True), 0);
  2104. FStatusMsg := Line;
  2105. end;
  2106. // format: 'HTTP/1.1 200 OK' // used by server
  2107. function THttpRequest.GetFirstResponseLine: string;
  2108. begin
  2109. if (FProtocol = '') then
  2110. FProtocol := 'HTTP/1.0';
  2111. if (FStatusCode = 0) then
  2112. StatusCode := 500; // Internal server error - did not set StatusCode...?
  2113. //
  2114. Result := Format('%s %d %s', [FProtocol, StatusCode, StatusMsg]);
  2115. end;
  2116. // format: 'GET /index.html HTTP/1.1' // used by client
  2117. function THttpRequest.GetFirstRequestLine: string;
  2118. begin
  2119. if (FMethod = '') then
  2120. FMethod := 'GET';
  2121. if (FUrl = '') then
  2122. FUrl := '/';
  2123. if (FProtocol = '') then
  2124. FProtocol := 'HTTP/1.0';
  2125. //
  2126. Result := Format('%s %s %s', [FMethod, FUrl, FProtocol]);
  2127. end;
  2128. function THttpRequest.MatchTag(Etags: string): boolean;
  2129. var
  2130. E: string;
  2131. begin
  2132. Result := False;
  2133. // If-Match header may specify more tags, comma-separated...
  2134. while (Etags <> '') do
  2135. begin
  2136. E := FetchQSepValue(Etags, ',');
  2137. if (E = '') then
  2138. continue;
  2139. if (E = '*') then
  2140. begin
  2141. Result := (Self.Etag <> '');
  2142. break;//exit;
  2143. end;
  2144. if (E = Self.Etag) then
  2145. begin
  2146. Result := True;
  2147. break;//exit;
  2148. end;
  2149. end;
  2150. end;
  2151. { THttpCookies }
  2152. constructor THttpCookies.Create;
  2153. begin
  2154. inherited Create(THttpCookie);
  2155. end;
  2156. function THttpCookies.GetCommaText: string;
  2157. var
  2158. i: integer;
  2159. begin
  2160. Result := '';
  2161. for i := 0 to Count - 1 do
  2162. Result := Result + Cookies[i].Name + '=' + Cookies[i].Value + ',';
  2163. System.Delete(Result, High(Result), 1);
  2164. end;
  2165. function THttpCookies.GetCookieItem(Index: integer): THttpCookie;
  2166. begin
  2167. Result := THttpCookie(inherited Items[Index]);
  2168. end;
  2169. function THttpCookies.GetValue(const Name: string): string;
  2170. var
  2171. Cookie: THttpCookie;
  2172. begin
  2173. Cookie := Find(Name);
  2174. if Assigned(Cookie) then
  2175. Result := Cookie.Value
  2176. else
  2177. Result := '';
  2178. end;
  2179. function THttpCookies.IndexOf(const Name: string): integer;
  2180. var
  2181. i: integer;
  2182. begin
  2183. for i := 0 to Count - 1 do
  2184. if SameText(Cookies[i].Name, Name) then
  2185. begin
  2186. Result := i;
  2187. exit;
  2188. end;
  2189. Result := -1;
  2190. end;
  2191. function THttpCookies.Find(const Name: string): THttpCookie;
  2192. var
  2193. Index: integer;
  2194. begin
  2195. Index := IndexOf(Name);
  2196. if (Index >= 0) then
  2197. Result := Cookies[Index]
  2198. else
  2199. Result := nil;
  2200. end;
  2201. procedure THttpCookies.LoadClientCookies(Headers: THeaderList);
  2202. begin
  2203. Clear;
  2204. Headers.EnumHeaders('Cookie', AddCookieValue, ';', 1);
  2205. //x: Headers.EnumHeaders('Cookie2',AddCookieValue,2);
  2206. end;
  2207. procedure THttpCookies.SaveServerCookies(Headers: THeaderList; const DefaultDomain, DefaultPath: string);
  2208. var
  2209. i: integer;
  2210. Cookie: THttpCookie;
  2211. begin
  2212. Headers.RemoveValue('Set-Cookie');
  2213. for i := 0 to Count - 1 do
  2214. begin
  2215. Cookie := Cookies[i];
  2216. if (Cookie.Domain = '') then
  2217. Cookie.Domain := Copy(DefaultDomain, 1, Pos(':', DefaultDomain) - 1);
  2218. if (Cookie.Path = '') then
  2219. Cookie.Path := DefaultPath;
  2220. Headers.AddValue('Set-Cookie', Cookie.GetServerCookie);
  2221. end;
  2222. end;
  2223. procedure THttpCookies.SetDefaultPath;
  2224. var
  2225. i: integer;
  2226. begin
  2227. for i := 0 to Count - 1 do
  2228. if Cookies[i].Path.IsEmpty then
  2229. Cookies[i].Path := '/';
  2230. end;
  2231. procedure THttpCookies.SetSameSite;
  2232. var
  2233. i: integer;
  2234. begin
  2235. for i := 0 to Count - 1 do
  2236. begin
  2237. Cookies[i].Secure := True;
  2238. Cookies[i].SameSite := True;
  2239. end;
  2240. end;
  2241. procedure THttpCookies.SetValue(const Name, Value: string);
  2242. var
  2243. Cookie: THttpCookie;
  2244. begin
  2245. Cookie := Find(Name);
  2246. if Assigned(Cookie) then
  2247. Cookie.Value := Value
  2248. else
  2249. begin
  2250. Cookie := THttpCookie(Add);
  2251. Cookie.Name := Name;
  2252. Cookie.Value := Value;
  2253. end;
  2254. end;
  2255. procedure THttpCookies.LoadServerCookies(Headers: THeaderList);
  2256. begin
  2257. Clear;
  2258. Headers.EnumHeaders('Set-Cookie', AddCookieValue, ',', 1);
  2259. Headers.EnumHeaders('Set-Cookie2', AddCookieValue, ',', 2);
  2260. end;
  2261. procedure THttpCookies.SaveClientCookies(Headers: THeaderList; const Path: string);
  2262. var
  2263. i: integer;
  2264. Cookie: THttpCookie;
  2265. begin
  2266. Headers.RemoveValue('Cookie');
  2267. for i := 0 to Count - 1 do
  2268. begin
  2269. Cookie := Cookies[i];
  2270. if (Path = '') or Cookie.MatchPath(Path) then
  2271. Headers.AddValue('Cookie', Cookie.GetClientCookie);
  2272. end;
  2273. end;
  2274. function THttpCookies.AddCookieValue(const Value: string; LParam: NativeUInt): boolean;
  2275. var
  2276. Cookie: THttpCookie;
  2277. begin
  2278. Cookie := THttpCookie.Create(nil);
  2279. if Cookie.ParseValue(Value, LParam) then
  2280. Cookie.Collection := Self
  2281. else
  2282. Cookie.Free;
  2283. //
  2284. Result := False; // all...
  2285. end;
  2286. procedure THttpCookies.MergeCookies(Source: THttpCookies);
  2287. var
  2288. i: integer;
  2289. Src, Dst: THttpCookie;
  2290. begin
  2291. for i := 0 to Source.Count - 1 do
  2292. begin
  2293. Src := Source[i];
  2294. Dst := Self.Find(Src.Name);
  2295. if (Dst = nil) then
  2296. Dst := THttpCookie.Create(nil);
  2297. Dst.Assign(Src);
  2298. Dst.Collection := Self;
  2299. end;
  2300. end;
  2301. { THttpCookie }
  2302. procedure THttpCookie.Assign(Source: TPersistent);
  2303. begin
  2304. if (Source is THttpCookie) then
  2305. begin
  2306. FName := THttpCookie(Source).FName;
  2307. FValue := THttpCookie(Source).FValue;
  2308. FDomain := THttpCookie(Source).FDomain;
  2309. FPath := THttpCookie(Source).FPath;
  2310. FExpires := THttpCookie(Source).FExpires;
  2311. FSecure := THttpCookie(Source).FSecure;
  2312. FMaxAge := THttpCookie(Source).FMaxAge;
  2313. end else
  2314. inherited;
  2315. end;
  2316. procedure THttpCookie.DeleteCookie;
  2317. begin
  2318. // RFC2109:
  2319. //Optional. The Max-Age attribute defines the lifetime of the
  2320. //cookie, in seconds. The delta-seconds value is a decimal non-
  2321. //negative integer. After delta-seconds seconds elapse, the client
  2322. //should discard the cookie. A value of zero means the cookie
  2323. //should be discarded immediately.
  2324. FMaxAge := '0';
  2325. end;
  2326. function QuoteValue(const Value: string): string;
  2327. var
  2328. p, len: integer;
  2329. begin
  2330. Result := Value;
  2331. //
  2332. len := Length(Result);
  2333. p := 1;
  2334. while (p <= len) do
  2335. begin
  2336. case Result[p] of
  2337. '"', '\':
  2338. begin
  2339. Insert('\', Result, p);
  2340. Inc(p);
  2341. Inc(len);
  2342. end;
  2343. end;
  2344. Inc(p);
  2345. end;
  2346. //
  2347. Result := '"' + Result + '"';
  2348. end;
  2349. function NeedsCookieValueQuoting(const S: string): boolean;
  2350. var
  2351. p: integer;
  2352. begin
  2353. if (S = '') then
  2354. begin
  2355. Result := True;
  2356. exit;
  2357. end;
  2358. //
  2359. p := Length(S);
  2360. while (p > 0) do
  2361. begin
  2362. case S[p] of
  2363. '"', '=', ';', ',', #1..' ':
  2364. begin
  2365. Result := True;
  2366. exit;
  2367. end;
  2368. end;
  2369. Dec(p);
  2370. end;
  2371. Result := False;
  2372. end;
  2373. function AddCookieProp(const Cookie, Name, Value: string; bnQuoted: boolean): string;
  2374. var
  2375. Sep, QVal: string;
  2376. begin
  2377. Result := Cookie;
  2378. if (Value <> '') then
  2379. begin
  2380. Sep := '';
  2381. if (Result <> '') then
  2382. Sep := '; ';
  2383. //
  2384. QVal := Value;
  2385. // values may be quoted, but do not need to be quoted...
  2386. if bnQuoted and NeedsCookieValueQuoting(Value) then
  2387. QVal := QuoteValue(Value);
  2388. //
  2389. Result := Result + Sep + Name + '=' + QVal;
  2390. end;
  2391. end;
  2392. function THttpCookie.GetServerCookie: string; // Set-Cookie: format... (for sending server->client)
  2393. begin
  2394. Result := AddCookieProp('', FName, FValue, True);
  2395. Result := AddCookieProp(Result, 'Version', FVersion, True);
  2396. Result := AddCookieProp(Result, 'Path', FPath, True);
  2397. Result := AddCookieProp(Result, 'Domain', FDomain, True);
  2398. Result := AddCookieProp(Result, 'Max-Age', FMaxAge, True);
  2399. Result := AddCookieProp(Result, 'Comment', FComment, True);
  2400. if FSameSite then
  2401. Result := AddCookieProp(Result, 'SameSite', 'none', False);
  2402. //
  2403. //Expires= is in this format: Wdy, DD-Mon-YY HH:MM:SS GMT
  2404. //in Netscape format, also must not use quotes or spaces elsewhere than in Expires...
  2405. //
  2406. if FSecure then
  2407. Result := Result + '; secure';
  2408. if FValue = '' then
  2409. Result := FName + '=;' + Result;
  2410. end;
  2411. function THttpCookie.GetText: string;
  2412. begin
  2413. Result := FName + '=' + ConvertUrlChars(FValue.Replace('\', ''));
  2414. end;
  2415. function THttpCookie.GetClientCookie: string; // Cookie: format... (for sending client->server)
  2416. begin
  2417. if (Version <> '') then
  2418. Result :=
  2419. AddCookieProp(AddCookieProp(AddCookieProp(AddCookieProp('', '$Version', Version, True), FName, FValue, True),
  2420. '$Path', FPath, True), '$Domain', FDomain, True)// RFC2109 format... should have Version='1'
  2421. // Cookie: $Version="1"; Name="Value"; $Path="Path", $Domain="Domain"
  2422. else
  2423. Result := AddCookieProp('', FName, FValue, False)// Simple Netscape format, just Name=Value, no quoting
  2424. //Result:=FName+'='+FValue;
  2425. ;
  2426. end;
  2427. function THttpCookie.ParseValue(Line: string; Version: NativeUInt): boolean;
  2428. var
  2429. Value, Name: string;
  2430. bnFirst, bnSpecial: boolean;
  2431. begin
  2432. bnFirst := True;
  2433. while (Line <> '') do
  2434. begin
  2435. Value := FetchQSepValue(Line, ';');
  2436. if (Value <> '') then
  2437. begin
  2438. Name := FetchToken(Value, '=', True);
  2439. //
  2440. if (Name <> '') and (Name[1] = '$') then
  2441. begin
  2442. bnSpecial := True;
  2443. Delete(Name, 1, 1);
  2444. end else
  2445. bnSpecial := False;
  2446. //
  2447. if bnFirst and not bnSpecial then
  2448. begin
  2449. FName := Name;
  2450. FValue := Value;
  2451. bnFirst := False;
  2452. end else if SameText(Name, 'path') then // do not localize...
  2453. FPath := Value
  2454. else
  2455. if SameText(Name, 'expires') then
  2456. FExpires := Value
  2457. else
  2458. if SameText(Name, 'domain') then
  2459. FDomain := Value
  2460. else
  2461. if SameText(Name, 'secure') then
  2462. FSecure := True
  2463. else
  2464. if SameText(Name, 'version') then
  2465. FVersion := Value// other values:
  2466. ;
  2467. end;
  2468. end;
  2469. Result := not bnFirst;
  2470. end;
  2471. function THttpCookie.MatchPath(const aPath: string): boolean;
  2472. var
  2473. Len: integer;
  2474. begin
  2475. Len := Length(Self.Path);
  2476. //
  2477. if (Length(aPath) >= Len) and SameHead(aPath, Self.Path) //and SameText(Copy(aPath,1,Len),Self.Path)
  2478. then
  2479. Result := True
  2480. else
  2481. Result := False;
  2482. end;
  2483. { TSynHttpServer }
  2484. constructor TSynHttpServer.Create(AOwner: TComponent);
  2485. begin
  2486. inherited;
  2487. Port := '80';
  2488. //
  2489. //FConnClass:=TSynTcpSrvConnection; // we are using generic connection class...
  2490. //
  2491. if not (csDesigning in ComponentState) then
  2492. OnCommand := HandleClientCommand;
  2493. end;
  2494. procedure TSynHttpServer.SetActive(Value: boolean);
  2495. begin
  2496. {$ifdef DEBUG}
  2497. if (Value=Self.Active) then
  2498. exit;
  2499. if Value then
  2500. Debug('%s http server on port %s',['Starting',Port])
  2501. else
  2502. Debug('%s http server on port %s',['Stoping',Port]);
  2503. {$endif DEBUG}
  2504. //
  2505. inherited;
  2506. //
  2507. {$ifdef DEBUG}
  2508. Debug('Done.');
  2509. {$endif DEBUG}
  2510. end;
  2511. (*function GetStreamSize(Stream: TStream): int64;
  2512. var
  2513. Pos: int64;
  2514. begin
  2515. Pos := Stream.Position;
  2516. Result := Stream.Size;
  2517. //
  2518. {$ifdef MSWINDOWS}
  2519. // Workarround for Delphi 5, where stream does not return Int64...
  2520. if (Stream is TFileStream) then begin
  2521. LARGE_INTEGER(Pos).HighPart:=0;
  2522. LARGE_INTEGER(Pos).LowPart:=SetFilePointer(TFileStream(Stream).Handle,0,@LARGE_INTEGER(Pos).HighPart,FILE_CURRENT);
  2523. LARGE_INTEGER(Result).HighPart:=0;
  2524. LARGE_INTEGER(Result).LowPart:=SetFilePointer(TFileStream(Stream).Handle,0,@LARGE_INTEGER(Result).HighPart,FILE_END);
  2525. //
  2526. SetFilePointer(TFileStream(Stream).Handle,LARGE_INTEGER(Pos).LowPart,@LARGE_INTEGER(Pos).HighPart,FILE_BEGIN);
  2527. end;
  2528. {$endif}
  2529. //
  2530. Result := Result - Pos;
  2531. end;
  2532. procedure StreamSeek(Stream: TStream; Offset: int64);
  2533. var
  2534. This: longint;
  2535. begin
  2536. // Workarround for Delphi 5, where TStream cannot seek by Int64...
  2537. while (Offset > 0) do
  2538. begin
  2539. if (Offset < $20000000) then
  2540. This := Offset
  2541. else
  2542. This := $20000000;
  2543. Dec(Offset, This);
  2544. Stream.Seek(This, soFromCurrent);
  2545. end;
  2546. end;*)
  2547. function ParseRangeRequest(S: string; var RangeStart, RangeLength: int64; const ContentSize: int64;
  2548. bnSizeKnown: boolean): boolean;
  2549. var
  2550. p: integer;
  2551. S1, S2: string;
  2552. RangeEnd: int64;
  2553. begin
  2554. Result := False;
  2555. // bytes=0-1000
  2556. // bytes=1000-
  2557. // bytes=-1000
  2558. // bytes=0-1000,2000-3000 this form is not parsed here and is ignored... this way we can avoid sending multipart/byte-ranges response...
  2559. //
  2560. if SameHead(S, 'bytes') //if SameText(Copy(S,1,5),'bytes')
  2561. then
  2562. begin
  2563. Delete(S, 1, 5);
  2564. DoTrim(S); // can have space: bytes = ...
  2565. if (S <> '') and (S[1] = '=') then
  2566. begin
  2567. Delete(S, 1, 1);
  2568. DoTrim(S);
  2569. end;
  2570. //
  2571. p := Pos('-', S);
  2572. if (p = 0) then
  2573. exit;
  2574. //
  2575. S1 := TrimCopy(S, 1, p - 1);
  2576. S2 := TrimCopy(S, p + 1, 63);
  2577. //
  2578. RangeStart := StrToInt64Def(S1, -1);
  2579. RangeEnd := StrToInt64Def(S2, -1);
  2580. //
  2581. if (S1 = '') then
  2582. begin
  2583. if (S2 = '') or not bnSizeKnown or (RangeEnd < 0) then
  2584. exit;
  2585. // bytes=-tailsize
  2586. RangeStart := ContentSize - RangeEnd;
  2587. RangeLength := RangeEnd;
  2588. Result := True;
  2589. end else
  2590. if (S2 = '') then
  2591. begin
  2592. // bytes=startpos-
  2593. if (RangeStart < 0) or not bnSizeKnown then
  2594. exit;
  2595. RangeLength := ContentSize - RangeStart;
  2596. Result := True;
  2597. end else
  2598. if (RangeStart >= 0) and (RangeEnd >= 0) then
  2599. begin
  2600. // bytes=startpos-endpos
  2601. RangeLength := RangeEnd - RangeStart + 1;
  2602. Result := True;
  2603. end;
  2604. end;
  2605. end;
  2606. // this function is the body of http request handling:
  2607. procedure TSynHttpServer.HandleClientCommand(Connection: TSynTcpSrvConnection; Command: string);
  2608. var
  2609. Request, Reply: THttpRequest;
  2610. begin
  2611. // Command is first line of request: GET /index.html HTTP/1.1
  2612. Request := THttpRequest.Create;
  2613. Reply := THttpRequest.Create;
  2614. try
  2615. ReadRequest(Connection, Request, Reply, Command);
  2616. DoHttpGet(Connection, Request, Reply);
  2617. //-------------------------------------------------------------------------
  2618. // Pass to application:
  2619. if (Reply = nil) then
  2620. Exit;// There is a chance for application to send reply, free it and give us NIL instead, to prevent further processing...
  2621. //-------------------------------------------------------------------------
  2622. SendReply(Connection, Request, Reply);
  2623. //
  2624. finally
  2625. Reply.Free;
  2626. Request.Free;
  2627. end;
  2628. end;
  2629. procedure TSynHttpServer.CreatePostStream(Request: THttpRequest);
  2630. begin
  2631. if Assigned(OnCreatePostStream) then
  2632. OnCreatePostStream(Self, Request, Request.FPostStream);
  2633. end;
  2634. procedure TSynHttpServer.ReadRequest(Connection: TSynTcpSrvConnection; Request, Reply: THttpRequest; Command: string);
  2635. var
  2636. bnContinue, bnHttp11: boolean;
  2637. S: string;
  2638. function PreparePostStream: boolean;
  2639. var
  2640. i: integer;
  2641. Size: int64;
  2642. begin
  2643. Result := False;
  2644. if (Request.TransferEncoding <> '') and (not SameText(Request.TransferEncoding, 'identity')) then
  2645. begin
  2646. if Pos('chunked', LowerCase(Request.TransferEncoding)) = 0 then
  2647. begin
  2648. Reply.StatusCode := 400; // bad request
  2649. //Reply.WriteHeader;
  2650. Connection.Terminate;
  2651. Exit;
  2652. end;
  2653. CreatePostStream(Request);
  2654. if Request.FPostStream = nil then
  2655. Request.FPostStream := TMemoryStream.Create;
  2656. Request.PostStream.Position := 0;
  2657. repeat
  2658. S := string(Connection.Socket.RecvString(cDefLineTimeout));
  2659. if (Connection.Socket.LastError <> 0) then
  2660. Exit;
  2661. i := Pos(';', S); {do not localize}
  2662. if i > 0 then
  2663. S := Copy(S, 1, i - 1);
  2664. Size := StrToInt64Def('$' + Trim(S), 0); {do not localize}
  2665. if Size = 0 then
  2666. Break;
  2667. Connection.Socket.RecvStreamSize(Request.PostStream, cDefLineTimeout, Size);
  2668. Connection.Socket.RecvString(cDefLineTimeout); // CRLF at end of chunk data
  2669. until False;
  2670. // skip trailer headers
  2671. repeat
  2672. until Connection.Socket.RecvString(cDefLineTimeout) = '';
  2673. Request.PostStream.Position := 0;
  2674. end
  2675. else if Request.ContentLength <> '' then
  2676. begin
  2677. CreatePostStream(Request);
  2678. if Request.FPostStream = nil then
  2679. Request.FPostStream := TMemoryStream.Create;
  2680. Request.PostStream.Position := 0;
  2681. if Request.ContentLength > '0' then
  2682. begin
  2683. Size := StrToInt64Def(Request.ContentLength, 0);
  2684. Connection.Socket.RecvStreamSize(Request.PostStream, cDefLineTimeout, Size);
  2685. Request.PostStream.Position := 0;
  2686. end;
  2687. end
  2688. // If HTTP Pipelining is used by the client, bytes may exist that belong to
  2689. // the NEXT request! We need to look at the CURRENT request and only check
  2690. // for misreported body data if a body is actually expected. GET and HEAD
  2691. // requests do not have bodies...
  2692. else if SameText(Request.Method, 'POST') or SameText(Request.Method, 'PUT') then
  2693. begin
  2694. // TODO: need to handle the case where the ContentType is 'multipart/...',
  2695. // which is self-terminating and does not strictly require the above headers...
  2696. if Connection.Socket.LineBuffer = '' then
  2697. Connection.Socket.CanReadEx(cDefLineTimeout);
  2698. if Connection.Socket.LineBuffer <> '' then
  2699. begin
  2700. Reply.StatusCode := 411; // length required
  2701. Connection.Terminate;
  2702. Exit;
  2703. end;
  2704. end;
  2705. Result := True;
  2706. end;
  2707. begin
  2708. //
  2709. // Connect objects:
  2710. Request.FConnection := Connection;
  2711. Reply.FConnection := Connection;
  2712. //
  2713. // Parse first line:
  2714. {$ifdef DEBUG} Debug('Command:'#13#10'%s',[Command]); {$endif}
  2715. Request.ParseFirstRequestLine(Command);
  2716. //
  2717. // Read rest of headers:
  2718. if not ReadHeadersFromSocket(Connection.Socket, Request.Headers,
  2719. {Connection.Socket.GetRecvTimeout}cDefLineTimeout) then
  2720. begin
  2721. Connection.Terminate;
  2722. Exit;
  2723. end;
  2724. Request.ApplyHeaders(True);
  2725. //
  2726. if (Request.Protocol >= 'HTTP/1.1') and SameHead(Request.Protocol, 'HTTP')
  2727. //and SameText(Copy(Request.Protocol,1,4),'HTTP')
  2728. then
  2729. begin
  2730. bnHttp11 := True;
  2731. Reply.FProtocol := 'HTTP/1.1'; // we are compliant...
  2732. //
  2733. S := Request.Headers['Expect'];
  2734. if (S <> '') then
  2735. begin
  2736. // RFC2616:
  2737. //A server that does not understand or is unable to comply with any of
  2738. //the expectation values in the Expect field of a request MUST respond
  2739. //with appropriate error status. The server MUST respond with a 417
  2740. //(Expectation Failed) status if any of the expectations cannot be met
  2741. //or, if there are other problems with the request, some other 4xx
  2742. //status.
  2743. bnContinue := SameText(S, '100-continue'); // we understand only this Expect value...
  2744. if Assigned(FOnExpect) then
  2745. FOnExpect(Self, Request, bnContinue);
  2746. //
  2747. if bnContinue then
  2748. begin
  2749. Reply.StatusCode := 100; // 100 continue
  2750. Connection.Socket.SendString(UTF8Encode(Reply.GetFirstResponseLine + #13#10#13#10));
  2751. end else
  2752. begin
  2753. // RFC2616:
  2754. //If it responds with a final status
  2755. //code, it MAY close the transport connection
  2756. Reply.StatusCode := 417; // Expectation failed
  2757. Connection.Socket.SendString(UTF8Encode(Reply.GetFirstResponseLine + #13#10#13#10));
  2758. Connection.Terminate;
  2759. Exit;
  2760. end;
  2761. end;
  2762. end else
  2763. if (Request.Protocol = 'HTTP/1.0') then
  2764. begin
  2765. Reply.FProtocol := 'HTTP/1.0';
  2766. bnHttp11 := False;
  2767. end else
  2768. begin
  2769. // Do not serve just any non-sense, written to our port...
  2770. // Chance for getting HTTP/0.9 request is very small,
  2771. // but chance for getting for ex. SMTP communication into the server port is much better...
  2772. Connection.Terminate;
  2773. Exit;
  2774. end;
  2775. //
  2776. // Read body:
  2777. if not PreparePostStream then
  2778. Exit;
  2779. if Assigned(Request.PostStream) and SameText(Request.Method, 'POST') then
  2780. begin
  2781. S := Request.ContentType;
  2782. if S.StartsWith('application/x-www-form-urlencoded', True) {or S.StartsWith('multipart/form-data', True)} then
  2783. with TStringStream.Create do
  2784. begin
  2785. CopyFrom(Request.PostStream, Request.PostStream.Size);
  2786. Request.FContent := DataString;
  2787. Free;
  2788. end;
  2789. end;
  2790. //
  2791. // Set some defaults:
  2792. Reply.StatusCode := 404; // default to Not-found...
  2793. if bnHttp11 then
  2794. begin
  2795. // HTTP/1.1 clients should default to keep-alive (rfc2616):
  2796. if not Request.Headers.HasValue('Connection', 'close') then
  2797. Reply.Headers['Connection'] := 'keep-alive'
  2798. else
  2799. Reply.Headers['Connection'] := 'close';
  2800. end else if Request.Headers.HasValue('Connection', 'keep-alive') then
  2801. Reply.Headers['Connection'] := 'keep-alive'
  2802. else
  2803. Reply.Headers['Connection'] := 'close'// HTTP/1.0 clients should default to close (rfc2616):
  2804. ;
  2805. //
  2806. // Cookies:
  2807. //??? Reply.Cookies.Assign(Request.Cookies);
  2808. //
  2809. // POST parameters:
  2810. if Request.FContent <> '' then
  2811. Request.ParsePostFormData;
  2812. //
  2813. Reply.Headers['Server'] := ServerValue;
  2814. end;
  2815. procedure TSynHttpServer.DoHttpGet(Connection: TSynTcpSrvConnection; Request, Reply: THttpRequest);
  2816. begin
  2817. if Assigned(FOnHttpGet) then
  2818. FOnHttpGet(Self, Connection, Request, Reply);
  2819. end;
  2820. function IsWithin(Value, Min, Max: integer): boolean;
  2821. begin
  2822. Result := (Value >= Min) and (Value <= Max);
  2823. end;
  2824. function ExtractUrlPath(const Url: string): string;
  2825. var
  2826. p: integer;
  2827. bnFound: boolean;
  2828. begin
  2829. Result := Url;
  2830. p := Pos('://', Result);
  2831. if (p > 0) then
  2832. begin
  2833. Delete(Result, 1, p + 2); // remove http://
  2834. p := Pos('/', Result);
  2835. if (p > 0) then
  2836. Delete(Result, 1, p); // remove hostname
  2837. end;
  2838. //
  2839. p := Pos('?', Result);
  2840. if (p = 0) then
  2841. p := Length(Result) + 1;
  2842. bnFound := False;
  2843. while (p > 1) do
  2844. begin
  2845. Dec(p);
  2846. if (Result[p] = '/') then
  2847. begin
  2848. SetLength(Result, p - 1);
  2849. bnFound := True;
  2850. break;
  2851. end;
  2852. end;
  2853. //
  2854. if not bnFound or (Result = '') then
  2855. Result := '/';
  2856. end;
  2857. procedure TSynHttpServer.SendReply(Connection: TSynTcpSrvConnection; Request, Reply: THttpRequest);
  2858. var
  2859. bnBody, bnSize: boolean;
  2860. S: string;
  2861. Size, RangeStart, RangeLength: int64;
  2862. Date, Date2: TDateTime;
  2863. function AlwaysUpdate(const Url: string): boolean;
  2864. begin
  2865. Result := (Url = '/') or (Url.ToLower.Contains('.html'));
  2866. end;
  2867. begin
  2868. if Reply.ResponseSent then
  2869. Exit;
  2870. // Adjust Reply:
  2871. //
  2872. // Cookies:
  2873. Reply.Cookies.SetDefaultPath;
  2874. if FHTTPSEnabled then
  2875. Reply.Cookies.SetSameSite;
  2876. Reply.Cookies.SaveServerCookies(Reply.Headers, Request.Host, ExtractUrlPath(Request.Url));
  2877. //
  2878. // Fill other values:
  2879. if (Reply.Headers['Date'] = '') then
  2880. Reply.Headers['Date'] := FormatHttpDate(Now, True);
  2881. //
  2882. // Content-Length and Transfer-Encoding:
  2883. if Reply.SendChunked then
  2884. begin
  2885. Reply.ContentLength := '';
  2886. Reply.TransferEncoding := 'chunked';
  2887. Size := -1;
  2888. bnSize := False;
  2889. end else
  2890. begin
  2891. S := Reply.ContentLength;
  2892. if (S = '') then
  2893. begin
  2894. // Fill Content-Length:
  2895. if (Reply.PostStream <> nil) then
  2896. begin
  2897. //Size:=Reply.ContentStream.Size;
  2898. Size := Reply.PostStream.Size;
  2899. bnSize := True;
  2900. end else
  2901. if (Reply.Content <> '') then
  2902. begin
  2903. Size := Length(UTF8Encode(Reply.Content));
  2904. bnSize := True;
  2905. end else
  2906. begin
  2907. Size := 0;
  2908. bnSize := False;
  2909. end;
  2910. //
  2911. Reply.ContentLength := IntToStr(Size);
  2912. //
  2913. end else
  2914. begin
  2915. // Content-Length was filled by application:
  2916. Size := StrToInt64Def(S, -1);
  2917. bnSize := (Size >= 0);
  2918. end;
  2919. end;
  2920. //
  2921. //? if (Reply.StatusCode=404) then Reply.Headers['Connection']:='close';
  2922. //
  2923. if IsWithin(Reply.StatusCode, 200, 299) and not AlwaysUpdate(Request.Url) then
  2924. begin
  2925. //
  2926. // Check If-Modified-Since:
  2927. S := Request.Headers['If-Modified-Since'];
  2928. if (S <> '') and ParseHttpDate(S, Date) then
  2929. begin
  2930. {$ifdef DEBUG}
  2931. Debug('If-Modified-Since: %s',[S]);
  2932. Debug('Last-Modified: %s',[Reply.Headers['Last-Modified']]);
  2933. {$endif DEBUG}
  2934. //
  2935. Date2 := Reply.LastModifiedUtc;
  2936. if (Date2 <> 0) and (Date2 > Date) then // is modified...
  2937. {$ifdef DEBUG}
  2938. {$endif DEBUG}
  2939. else
  2940. begin
  2941. // Is not modified...
  2942. Reply.StatusCode := 304; // Not Modified
  2943. //!!!TODO/bug
  2944. // mozilla hangs in transfer, when it gets the 304 responses??
  2945. //if (Copy(Request.Headers['User-Agent'], 1, 7) = 'Mozilla') then
  2946. // Reply.Headers['Connection'] := 'close';
  2947. end;
  2948. end else
  2949. begin
  2950. {$ifdef DEBUG}
  2951. if (S<>'') then
  2952. Debug('Failed parse date "%s"',[S]);
  2953. {$endif DEBUG}
  2954. //
  2955. S := Request.Headers['If-Unmodified-Since'];
  2956. if (S <> '') and ParseHttpDate(S, Date) then
  2957. begin
  2958. Date2 := Reply.LastModifiedUtc;
  2959. if (Date2 <> 0) and (Date2 > Date) then
  2960. Reply.StatusCode := 412// is modified
  2961. // Precondition Failed
  2962. ;
  2963. end;
  2964. end;
  2965. end;
  2966. //
  2967. if IsWithin(Reply.StatusCode, 200, 299) then
  2968. begin
  2969. //
  2970. // Check If-Range - if the condition fails, we will ignore Range: header...
  2971. S := Request.Headers['If-Range'];
  2972. if (S <> '') then
  2973. if (CharInSet(S[1], ['w', 'W'])) and (S[2] = '/') // W/"tag"
  2974. or (S[1] = '"') // "tag"
  2975. then
  2976. begin
  2977. if not Request.Headers.HasValue('Etag', S) then
  2978. Request.Headers['Range'] := ''; // does not have this Etag...
  2979. end else if ParseHttpDate(S, Date) then
  2980. begin
  2981. Date2 := Reply.LastModifiedUtc;
  2982. if (Date2 = 0) or (Date2 <= Date) then // is not modified since...
  2983. else
  2984. begin
  2985. // was modified since...
  2986. Request.Headers['Range'] := ''; // will send whole...
  2987. end;
  2988. end else
  2989. Request.Headers['Range'] := ''// Http-date: like If-Unmodified-Since...
  2990. // we do not understand If-Range header, so we will send whole body...
  2991. // If-Range = "If-Range" ":" ( entity-tag | HTTP-date )
  2992. ;
  2993. //
  2994. // Check Range: header
  2995. RangeStart := 0;
  2996. RangeLength := 0;
  2997. S := Request.Headers['Range'];
  2998. if (S <> '') and ParseRangeRequest(S, RangeStart, RangeLength, Size, bnSize) then
  2999. if (bnSize and (RangeStart >= Size)) or (RangeLength <= 0) then
  3000. begin
  3001. Reply.StatusCode := 416; // Requested Range Not Satisfiable
  3002. Reply.ContentLength := '';
  3003. if bnSize then
  3004. Reply.Headers['Content-Range'] := Format('*/%d', [Size]); // we SHOULD send this with 416 code...
  3005. Size := 0; // do not send body... //we will not send body, filtered also below...
  3006. end else
  3007. begin
  3008. // Valid range:
  3009. if bnSize then
  3010. S := IntToStr(Size)
  3011. else
  3012. S := '*';
  3013. Reply.StatusCode := 206; // Partial Content
  3014. Reply.Headers['Content-Range'] :=
  3015. Format('bytes %d-%d/%s', [RangeStart, RangeStart + RangeLength - 1, S]);
  3016. if bnSize then
  3017. Reply.ContentLength := IntToStr(RangeLength);
  3018. //
  3019. if (RangeStart <> 0) then
  3020. if (Reply.PostStream <> nil) then
  3021. Reply.PostStream.Seek(RangeStart, soCurrent)
  3022. else
  3023. if (Reply.Content <> '') then
  3024. Delete(Reply.FContent, 1, RangeStart);
  3025. //
  3026. if (RangeLength <> 0) then
  3027. begin
  3028. Size := RangeLength;
  3029. if (Reply.PostStream = nil) and (Reply.Content <> '') and (Size < Length(Reply.Content)) then
  3030. SetLength(Reply.FContent, Size);
  3031. end;
  3032. end//
  3033. ;
  3034. end;
  3035. //
  3036. if IsWithin(Reply.StatusCode, 200, 299) then
  3037. begin
  3038. // Check Etag headers (If-Match, If-None-Match)
  3039. S := Request.Headers['If-Match'];
  3040. if (S <> '') then
  3041. if not Reply.MatchTag(S) then
  3042. Reply.StatusCode := 412// Precondition Failed
  3043. // reply may have more tags, comma-separated, some week...
  3044. // also If-Match may specify more tags...
  3045. ;
  3046. S := Request.Headers['If-None-Match'];
  3047. if (S <> '') then
  3048. if Reply.MatchTag(S) then
  3049. Reply.StatusCode := 412// Precondition Failed
  3050. ;
  3051. end;
  3052. //
  3053. //-------------------------------------------------------------------------
  3054. // Write reply to client:
  3055. S := Reply.GetFirstResponseLine + #13#10 + Reply.Headers.Text + #13#10; // include 1 empty line after headers...
  3056. {$ifdef DEBUG}Debug('Response headers:'#13#10'%s',[S]);{$endif}
  3057. Connection.Socket.SendString(UTF8Encode(S));
  3058. Reply.ResponseSent := True;
  3059. if (Connection.Socket.LastError <> 0) then
  3060. begin
  3061. Connection.Terminate;
  3062. Exit;
  3063. end;
  3064. //
  3065. bnBody := True;
  3066. if SameText(Request.Method, 'HEAD') then
  3067. bnBody := False // MUST NOT send entity body with HEAD, but should send Content-Length...
  3068. else
  3069. case Reply.StatusCode of
  3070. 412, // this is not in RFC, but we will not send entity body with 412 precondition failed anyway...
  3071. 416, // this is not in RFC, but we will not send entity body with 416 code (Requested Range Not Satisfiable) anyway...
  3072. 100..199, 204, 304:
  3073. begin
  3074. bnBody := False; // we MUST NOT send entity body with these status-codes...
  3075. // Do not send Content-Length and Content-Type fields
  3076. Reply.Headers['Content-Length'] := '';
  3077. Reply.Headers['Content-Type'] := '';
  3078. end;
  3079. end;
  3080. //
  3081. if bnBody then
  3082. begin
  3083. // Send body:
  3084. if (Reply.PostStream <> nil) then
  3085. SendSocketStream(Connection.Socket, Reply.PostStream, Size, Reply.SendChunked)
  3086. //x: we cannot use this, since it uses Stream.Size: Connection.Socket.SendStreamRaw(Reply.ContentStream);
  3087. else
  3088. if (Reply.Content <> '') then
  3089. if not Reply.SendChunked then
  3090. Connection.Socket.SendString(UTF8Encode(Reply.Content))
  3091. else
  3092. begin
  3093. // Send 1 chunk:
  3094. Connection.Socket.SendString(UTF8Encode(Format('%x'#13#10, [Length(Reply.Content)])));
  3095. if (Connection.Socket.LastError = 0) then
  3096. Connection.Socket.SendString(UTF8Encode(Reply.Content));
  3097. if (Connection.Socket.LastError = 0) then
  3098. Connection.Socket.SendString('0'#13#10#13#10);
  3099. end;
  3100. //
  3101. if (Connection.Socket.LastError <> 0) then
  3102. begin
  3103. Connection.Terminate;
  3104. exit;
  3105. end;
  3106. end;
  3107. //
  3108. if Reply.Headers.HasValue('Connection', 'close') then
  3109. Connection.Terminate;
  3110. end;
  3111. procedure TSynHttpServer.InitHttps(const CertFile, KeyFile, KeyPassword, CaCertFile: string);
  3112. begin
  3113. if not FileExists(CertFile) or not FileExists(KeyFile) then
  3114. Exit;
  3115. FCertFile := CertFile;
  3116. FKeyFile := KeyFile;
  3117. FKeyPass := KeyPassword;
  3118. FCaCertFile := CaCertFile;
  3119. //FSynapseServer.Socket.SSL.CertCAFile := ExtractFilePath(ParamStr(0)) + 's_cabundle.pem';
  3120. FSynapseServer.Socket.SSL.CertificateFile := FCertFile;
  3121. FSynapseServer.Socket.SSL.PrivateKeyFile := FKeyFile;
  3122. FSynapseServer.Socket.SSL.KeyPassword := FKeyPass;
  3123. FSynapseServer.Socket.SSL.VerifyCert := True;
  3124. //
  3125. end;
  3126. initialization
  3127. ContentTypes := TStringList.Create;
  3128. ContentTypes.Sorted := True;
  3129. RegisterInternalContentTypes;
  3130. finalization
  3131. FreeAndNil(ContentTypes);
  3132. end.