BrookUtility.pas 14 KB

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