BrookUtility.pas 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472
  1. (* _ _
  2. * | |__ _ __ ___ ___ | | __
  3. * | '_ \| '__/ _ \ / _ \| |/ /
  4. * | |_) | | | (_) | (_) | <
  5. * |_.__/|_| \___/ \___/|_|\_\
  6. *
  7. * Microframework which helps to develop web Pascal applications.
  8. *
  9. * Copyright (c) 2012-2021 Silvio Clecio <[email protected]>
  10. *
  11. * Brook framework is free software; you can redistribute it and/or
  12. * modify it under the terms of the GNU Lesser General Public
  13. * License as published by the Free Software Foundation; either
  14. * version 2.1 of the License, or (at your option) any later version.
  15. *
  16. * Brook framework is distributed in the hope that it will be useful,
  17. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  18. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  19. * Lesser General Public License for more details.
  20. *
  21. * You should have received a copy of the GNU Lesser General Public
  22. * License along with Brook framework; if not, write to the Free Software
  23. * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  24. *)
  25. { Utility functions of the framework. }
  26. unit BrookUtility;
  27. {$I BrookDefines.inc}
  28. interface
  29. uses
  30. RTLConsts,
  31. SysUtils,
  32. DateUtils,
  33. TypInfo,
  34. SyncObjs,
  35. {$IFDEF FPC}
  36. SHA1,
  37. HttpProtocol,
  38. {$ELSE}
  39. System.Hash,
  40. System.NetEncoding,
  41. {$ENDIF}
  42. Marshalling,
  43. libsagui;
  44. const
  45. { Primitive kinds. }
  46. tkPrimitives = tkProperties -
  47. {$IFDEF FPC}
  48. [tkArray..tkObject] - [tkInterfaceRaw] - [tkProcVar] - [tkHelper..tkPointer]
  49. {$ELSE}
  50. [tkClass] - [tkArray..tkInterface] -
  51. [tkClassRef..{$IF CompilerVersion >= 33.0}tkMRecord{$ELSE}tkProcedure{$ENDIF}]
  52. {$ENDIF};
  53. type
  54. { Event signature used by stuff that handles errors.
  55. @param(ASender[in] Sender object.)
  56. @param(AException[in] Exception object.) }
  57. TBrookErrorEvent = procedure(ASender: TObject;
  58. AException: Exception) of object;
  59. { Allows to lock other threads from accessing a block of code. }
  60. TBrookLocker = class
  61. private
  62. FMutex: TCriticalSection;
  63. FActive: Boolean;
  64. procedure SetActive(AValue: Boolean);
  65. protected
  66. property Mutex: TCriticalSection read FMutex;
  67. function CreateMutex: TCriticalSection; virtual;
  68. public
  69. { Creates an instance of @code(TBrookLocker). }
  70. constructor Create; virtual;
  71. { Frees an instance of @code(TBrookLocker). }
  72. destructor Destroy; override;
  73. { Locks all other threads. }
  74. procedure Lock; virtual;
  75. { Unlocks all other threads. }
  76. procedure Unlock; virtual;
  77. { Tries to lock all other threads. }
  78. function TryLock: Boolean; virtual;
  79. { Activates the locker. (Default: @True) }
  80. property Active: Boolean read FActive write SetActive;
  81. end;
  82. { Global Sagui object containing general purpose functions. }
  83. Sagui = record
  84. { Returns the library version number.
  85. @returns(Library version packed into a single integer.) }
  86. class function Version: Cardinal; overload; static;
  87. { Returns the library version number.
  88. @param(AMajor[out] Major number.)
  89. @param(AMinor[out] Minor number.)
  90. @param(APatch[out] Patch number.)
  91. @returns(Library version packed into a single integer.) }
  92. class function Version(out AMajor, AMinor: Byte;
  93. out APatch: SmallInt): Cardinal; overload; static;
  94. { Returns the library version number as string in the
  95. format @code(<MAJOR>.<MINOR>.<PATCH>).
  96. @returns(Library version packed into a static string.) }
  97. class function VersionStr: string; static;
  98. { Allocates a new memory space.
  99. @param(ASize[in] Memory size to be allocated.)
  100. @returns(Pointer of the allocated zero-initialized memory.
  101. @bold(Returns values:)
  102. @definitionList(
  103. @itemLabel(@code(nil))
  104. @item(If size is @code(0) or no memory space.)
  105. )
  106. ) }
  107. class function Malloc(ASize: NativeUInt): Pointer; static;
  108. { Allocates a new zero-initialized memory space.
  109. @param(ASize[in] Memory size to be allocated.)
  110. @returns(Pointer of the allocated zero-initialized memory.
  111. @bold(Returns values:)
  112. @definitionList(
  113. @itemLabel(@code(nil))
  114. @item(If size is @code(0) or no memory space.)
  115. )
  116. ) }
  117. class function Alloc(ASize: NativeUInt): Pointer; static;
  118. { Reallocates an existing memory block.
  119. @param(APointer[in] Pointer of the memory to be reallocated.)
  120. @param(ASize[in] Memory size to be allocated.)
  121. @returns(Pointer of the reallocated memory.) }
  122. class function Realloc(APointer: Pointer;
  123. ASize: NativeUInt): Pointer; static;
  124. { Frees a memory space previous allocated by @code(Sagui.Malloc),
  125. @link(Sagui.Alloc) or @code(Sagui.Realloc).
  126. @param(APointer[in] Pointer of the memory to be freed.) }
  127. class procedure Free(APointer: Pointer); static;
  128. { Returns string describing an error number.
  129. @param(AErrorNum[in] Error number.)
  130. @param(AErrorMsg[out] Referenced string to store the error message.)
  131. @param(AErrorLen[in] Length of the error message.) }
  132. class procedure StrError(AErrorNum: Integer; out AErrorMsg: string;
  133. AErrorLen: Integer); overload; static; {$IFNDEF DEBUG}inline;{$ENDIF}
  134. { Returns string describing an error number.
  135. @param(AErrorNum[in] Error number.)
  136. @returns(Static string describing the error.) }
  137. class function StrError(AErrorNum: Integer): string; overload; static;
  138. { Checks if a string is an HTTP post method.
  139. @param(AMethod[in] HTTP verb.)
  140. @returns(True if given method is POST, PUT, DELETE or OPTIONS.) }
  141. class function IsPost(const AMethod: string): Boolean; static;
  142. { Extracts the entry-point of a path or resource. For example, given a path
  143. @code(/api1/customer), the part considered as entry-point is
  144. @code(/api1).
  145. @param(APath[in] Path as static string.)
  146. @returns(Entry-point as static string.) }
  147. class function ExtractEntryPoint(const APath: string): string; static;
  148. { Returns the system temporary directory.
  149. @returns(Temporary directory as static string.) }
  150. class function TmpDir: string; static;
  151. { Indicates the end-of-read processed in
  152. @code(TBrookHTTPResponse.SendStream).
  153. @param(AError[in] @True to return a value indicating a stream
  154. reading error.)
  155. @returns(Value to end a stream reading.) }
  156. class function EOR(AError: Boolean): NativeInt; static;
  157. { Obtains the IP of a socket handle into a string.
  158. @param(ASocket[in] Socket handle.)
  159. @return(Formatted IP into a string.) }
  160. class function IP(ASocket: Pointer): string; static;
  161. end;
  162. { Global Brook object containing general purpose functions. }
  163. Brook = record
  164. public const
  165. {$IFNDEF FPC}
  166. {$WRITEABLECONST ON}
  167. {$ENDIF}
  168. { Holds the name of days as 'Aaa' format. }
  169. DAYS: array[1..7] of string = ('Sun', 'Mon', 'Tue', 'Wed', 'Thu',
  170. 'Fri', 'Sat');
  171. { Holds the name of months as 'Aaa' format. }
  172. MONTHS: array[1..12] of string = ('Jan', 'Feb', 'Mar', 'Apr', 'May',
  173. 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec');
  174. {$IFNDEF FPC}
  175. {$WRITEABLECONST OFF}
  176. {$ENDIF}
  177. { Fixes a path by including the leading path delimiter and excluding the
  178. trailing one.
  179. @param(APath[in] Path as static string.)
  180. @returns(Fixed path, e.g.: path -> /path and /path/ -> /path) }
  181. class function FixPath(const APath: string): string; static;
  182. {$IFNDEF DEBUG}inline;{$ENDIF}
  183. { Extracts and fixes an entry-point by including the leading path delimiter
  184. and excluding the trailing one.
  185. @param(APath[in] Path as static string.)
  186. @returns(Fixed entry-point, e.g.: /foo/bar -> /foo ) }
  187. class function FixEntryPoint(const APath: string): string; static;
  188. {$IFNDEF DEBUG}inline;{$ENDIF}
  189. { Converts a given local time to UTC (Coordinated Universal Time).
  190. @param(ADateTime[in] Local date/time.)
  191. @returns(Local time converted to UTC.) }
  192. class function DateTimeToUTC(ADateTime: TDateTime): TDateTime; static;
  193. {$IFNDEF DEBUG}inline;{$ENDIF}
  194. { Converts a given local time to GMT (Greenwich Mean Time).
  195. @param(ADateTime[in] Local date/time.)
  196. @returns(Local time converted to GMT string.) }
  197. class function DateTimeToGMT(ADateTime: TDateTime): string; static;
  198. {$IFNDEF DEBUG}inline;{$ENDIF}
  199. { Generates a given string to SHA-1 (Secure Hash Algorithm 1).
  200. @param(S[in] String to generate the SHA-1.)
  201. @returns(Generated SHA-1 as static string.) }
  202. class function SHA1(const S: string): string; static;
  203. {$IFNDEF DEBUG}inline;{$ENDIF}
  204. end;
  205. { HTTP verbs enumeration. }
  206. TBrookHTTPRequestMethod = (rmUnknown, rmGET, rmPOST, rmPUT, rmDELETE, rmPATCH,
  207. rmOPTIONS, rmHEAD);
  208. { Set of HTTP verbs. }
  209. TBrookHTTPRequestMethods = set of TBrookHTTPRequestMethod;
  210. { Type helper for HTTP verb conversion. }
  211. TBrookHTTPRequestMethodHelper = record helper for TBrookHTTPRequestMethod
  212. public const
  213. { Holds the name of HTTP verbs. }
  214. METHODS: array[TBrookHTTPRequestMethod] of string = ('Unknown', 'GET',
  215. 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS', 'HEAD');
  216. public
  217. { Converts a @code(TBrookHTTPRequestMethod) to string. }
  218. function ToString: string; inline;
  219. { Returns a @code(TBrookHTTPRequestMethod) from a string. }
  220. function FromString(const AMethod: string): TBrookHTTPRequestMethod;
  221. {$IFNDEF DEBUG}inline;{$ENDIF}
  222. end;
  223. implementation
  224. { TBrookLocker }
  225. constructor TBrookLocker.Create;
  226. begin
  227. inherited Create;
  228. FMutex := CreateMutex;
  229. FActive := True;
  230. end;
  231. destructor TBrookLocker.Destroy;
  232. begin
  233. FMutex.Free;
  234. inherited Destroy;
  235. end;
  236. function TBrookLocker.CreateMutex: TCriticalSection;
  237. begin
  238. Result := TCriticalSection.Create;
  239. end;
  240. procedure TBrookLocker.SetActive(AValue: Boolean);
  241. begin
  242. if FActive = AValue then
  243. Exit;
  244. FMutex.Acquire;
  245. try
  246. FActive := AValue;
  247. finally
  248. FMutex.Release;
  249. end;
  250. end;
  251. procedure TBrookLocker.Lock;
  252. begin
  253. if FActive then
  254. FMutex.Acquire;
  255. end;
  256. procedure TBrookLocker.Unlock;
  257. begin
  258. if FActive then
  259. FMutex.Release;
  260. end;
  261. function TBrookLocker.TryLock: Boolean;
  262. begin
  263. Result := FActive and FMutex.TryEnter;
  264. end;
  265. { Sagui }
  266. class function Sagui.Version: Cardinal;
  267. begin
  268. SgLib.Check;
  269. Result := sg_version;
  270. end;
  271. class function Sagui.Version(out AMajor, AMinor: Byte;
  272. out APatch: SmallInt): Cardinal;
  273. begin
  274. SgLib.Check;
  275. Result := sg_version;
  276. AMajor := (Result shr 16) and $FF;
  277. AMinor := (Result shr 8) and $FF;
  278. APatch := Result and $FF;
  279. end;
  280. class function Sagui.VersionStr: string;
  281. begin
  282. SgLib.Check;
  283. Result := TMarshal.ToString(sg_version_str);
  284. end;
  285. class function Sagui.Malloc(ASize: NativeUInt): Pointer;
  286. begin
  287. SgLib.Check;
  288. Result := sg_malloc(ASize);
  289. end;
  290. class function Sagui.Alloc(ASize: NativeUInt): Pointer;
  291. begin
  292. SgLib.Check;
  293. Result := sg_alloc(ASize);
  294. end;
  295. class function Sagui.Realloc(APointer: Pointer; ASize: NativeUInt): Pointer;
  296. begin
  297. SgLib.Check;
  298. Result := sg_realloc(APointer, ASize);
  299. end;
  300. class procedure Sagui.Free(APointer: Pointer);
  301. begin
  302. SgLib.Check;
  303. sg_free(APointer);
  304. end;
  305. class procedure Sagui.StrError(AErrorNum: Integer; out AErrorMsg: string;
  306. AErrorLen: Integer);
  307. var
  308. P: array[0..Pred(SG_ERR_SIZE)] of cchar;
  309. begin
  310. SgLib.Check;
  311. P[0] := 0;
  312. sg_strerror(AErrorNum, @P[0], AErrorLen);
  313. AErrorMsg := TMarshal.ToString(@P[0]).TrimRight;
  314. end;
  315. class function Sagui.StrError(AErrorNum: Integer): string;
  316. begin
  317. Sagui.StrError(AErrorNum, Result, SG_ERR_SIZE);
  318. end;
  319. class function Sagui.IsPost(const AMethod: string): Boolean;
  320. var
  321. M: TMarshaller;
  322. begin
  323. SgLib.Check;
  324. Result := sg_is_post(M.ToCString(AMethod));
  325. end;
  326. class function Sagui.ExtractEntryPoint(const APath: string): string;
  327. var
  328. M: TMarshaller;
  329. S: Pcchar;
  330. begin
  331. SgLib.Check;
  332. S := sg_extract_entrypoint(M.ToCString(APath));
  333. try
  334. Result := TMarshal.ToString(S);
  335. finally
  336. sg_free(S);
  337. end;
  338. end;
  339. class function Sagui.TmpDir: string;
  340. var
  341. S: Pcchar;
  342. begin
  343. SgLib.Check;
  344. S := sg_tmpdir;
  345. try
  346. Result := TMarshal.ToString(S);
  347. finally
  348. sg_free(S);
  349. end;
  350. end;
  351. class function Sagui.EOR(AError: Boolean): NativeInt;
  352. begin
  353. SgLib.Check;
  354. Result := sg_eor(AError);
  355. end;
  356. class function Sagui.IP(ASocket: Pointer): string;
  357. var
  358. P: array[0..45] of cchar;
  359. begin
  360. if not Assigned(ASocket) then
  361. raise EArgumentNilException.CreateFmt(SParamIsNil, ['ASocket']);
  362. SgLib.Check;
  363. SgLib.CheckLastError(sg_ip(ASocket, @P[0], SizeOf(P)));
  364. Result := TMarshal.ToString(@P[0]);
  365. end;
  366. { Brook }
  367. class function Brook.FixPath(const APath: string): string;
  368. begin
  369. Result := APath;
  370. if not APath.StartsWith('/') then
  371. Result := Concat('/', Result);
  372. if (Length(APath) > SizeOf(Char)) and Result.EndsWith('/') then
  373. SetLength(Result, Length(Result) - Length('/'));
  374. end;
  375. class function Brook.FixEntryPoint(const APath: string): string;
  376. var
  377. PS: TArray<string>;
  378. begin
  379. PS := APath.Split(['/'], TStringSplitOptions.ExcludeEmpty);
  380. Result := '/';
  381. if Length(PS) > 0 then
  382. Result := Concat(Result, PS[0]);
  383. end;
  384. class function Brook.DateTimeToUTC(ADateTime: TDateTime): TDateTime;
  385. begin
  386. Result :=
  387. {$IFDEF FPC}
  388. LocalTimeToUniversal
  389. {$ELSE}
  390. TTimeZone.Local.ToUniversalTime
  391. {$ENDIF}(ADateTime);
  392. end;
  393. class function Brook.DateTimeToGMT(ADateTime: TDateTime): string;
  394. var
  395. Y, M, D: Word;
  396. begin
  397. DecodeDate(ADateTime, Y, M, D);
  398. DateTimeToString(Result, Format('"%s", dd "%s" yyy hh":"mm":"ss "GMT"', [
  399. DAYS[DayOfWeek(ADateTime)], MONTHS[M]]), ADateTime);
  400. end;
  401. class function Brook.SHA1(const S: string): string;
  402. begin
  403. Result :=
  404. {$IFDEF FPC}SHA1Print(SHA1String(S)){$ELSE}THashSHA1.GetHashString(S){$ENDIF};
  405. end;
  406. { TBrookHTTPRequestMethodHelper }
  407. function TBrookHTTPRequestMethodHelper.ToString: string;
  408. begin
  409. Result := METHODS[Self];
  410. end;
  411. function TBrookHTTPRequestMethodHelper.FromString(
  412. const AMethod: string): TBrookHTTPRequestMethod;
  413. var
  414. M: string;
  415. I: TBrookHTTPRequestMethod;
  416. begin
  417. M := AMethod.ToUpper;
  418. for I := Low(METHODS) to High(METHODS) do
  419. if SameStr(M, METHODS[I]) then
  420. Exit(I);
  421. Result := rmUnknown;
  422. end;
  423. end.