From 7420015c5ccd6ed0328131e99e546e9a9b341609 Mon Sep 17 00:00:00 2001 From: Daniele Teti Date: Thu, 9 May 2024 23:50:01 +0200 Subject: [PATCH] Sqids support - base classes + converter mechanism. Added ":sqid" converter. --- ideexpert/DMVC.Expert.CodeGen.Commands.pas | 28 +- packages/d120/dmvcframeworkRT.dpk | 3 +- packages/d120/dmvcframeworkRT.dproj | 1 + samples/serversentevents/SSESample.dpr | 9 +- samples/serversentevents/SSESample.dproj | 381 ++++-- samples/serversentevents2/WebModuleU.dfm | 1 - samples/serversentevents2/WebModuleU.pas | 133 +-- .../serversentevents2/serversentevents2.dproj | 381 ++++-- .../serversentevents2sender.dproj | 519 ++++++--- .../serversentevents2viewer.dproj | 519 ++++++--- samples/sqids/ControllerU.pas | 90 ++ samples/sqids/EntityU.pas | 36 + samples/sqids/ServicesU.pas | 44 + samples/sqids/WebModuleU.dfm | 7 + samples/sqids/WebModuleU.pas | 82 ++ samples/sqids/sqids_sample.dpr | 80 ++ samples/sqids/sqids_sample.dproj | 1034 +++++++++++++++++ sources/MVCFramework.Commons.pas | 55 +- sources/MVCFramework.Router.pas | 104 +- sources/MVCFramework.pas | 18 +- sources/blocklist.inc | 564 +++++++++ sources/sqids.pas | 396 +++++++ .../general/Several/DMVCFrameworkTests.dpr | 3 + .../general/Several/DMVCFrameworkTests.dproj | 5 + unittests/general/Several/FrameworkTestsU.pas | 20 +- unittests/general/Several/LiveServerTestU.pas | 39 + unittests/general/TestServer/TestServer.dpr | 2 + unittests/general/TestServer/TestServer.dproj | 5 + .../TestServer/TestServerControllerU.pas | 32 + 29 files changed, 3846 insertions(+), 745 deletions(-) create mode 100644 samples/sqids/ControllerU.pas create mode 100644 samples/sqids/EntityU.pas create mode 100644 samples/sqids/ServicesU.pas create mode 100644 samples/sqids/WebModuleU.dfm create mode 100644 samples/sqids/WebModuleU.pas create mode 100644 samples/sqids/sqids_sample.dpr create mode 100644 samples/sqids/sqids_sample.dproj create mode 100644 sources/blocklist.inc create mode 100644 sources/sqids.pas diff --git a/ideexpert/DMVC.Expert.CodeGen.Commands.pas b/ideexpert/DMVC.Expert.CodeGen.Commands.pas index 9ed340a1..b0b2bafe 100644 --- a/ideexpert/DMVC.Expert.CodeGen.Commands.pas +++ b/ideexpert/DMVC.Expert.CodeGen.Commands.pas @@ -177,6 +177,8 @@ type end; TUnitMainBeginEndCommand = class(TCustomCommand) + private + function GetScrambledAlphabet: String; public procedure ExecuteImplementation( Section: TStringBuilder; @@ -212,7 +214,6 @@ type implementation - { TUnitUsesCommand } procedure TUnitUsesCommand.ExecuteInterface(Section: TStringBuilder; @@ -982,6 +983,8 @@ begin .AppendLine(' // When MVCSerializeNulls = False empty nullables and nil are not serialized at all.') .AppendLine(' MVCSerializeNulls := True;') .AppendLine(' UseConsoleLogger := True;') + .AppendLine(' TMVCSqids.SQIDS_ALPHABET := dotEnv.Env(''dmvc.sqids.alphabet'', ''' + GetScrambledAlphabet + ''');') + .AppendLine(' TMVCSqids.SQIDS_MIN_LENGTH := dotEnv.Env(''dmvc.sqids.min_length'', 6);') .AppendLine .AppendLine(' LogI(''** DMVCFramework Server ** build '' + DMVCFRAMEWORK_VERSION);') .AppendLine(' try') @@ -1055,6 +1058,29 @@ begin end; +function TUnitMainBeginEndCommand.GetScrambledAlphabet: String; +const + DEFAULT_ALPHABET = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; +var + I: Integer; + lIdx1: Integer; + lSize: Integer; + lIdx2: Integer; + lTmp: Char; +begin + Randomize; + Result := DEFAULT_ALPHABET; + lSize := Length(Result); + for I := 1 to 100 do + begin + lIdx1 := Random(lSize) + 1; + lIdx2 := Random(lSize) + 1; + lTmp := Result[lIdx1]; + Result[lIdx1] := Result[lIdx2]; + Result[lIdx2] := lTmp; + end; +end; + { TUnitRunServerProcBody } procedure TUnitRunServerProcBody.ExecuteImplementation(Section: TStringBuilder; diff --git a/packages/d120/dmvcframeworkRT.dpk b/packages/d120/dmvcframeworkRT.dpk index 28dc1ff6..8016eb93 100644 --- a/packages/d120/dmvcframeworkRT.dpk +++ b/packages/d120/dmvcframeworkRT.dpk @@ -117,7 +117,8 @@ contains MVCFramework.Serializer.URLEncoded in '..\..\sources\MVCFramework.Serializer.URLEncoded.pas', MVCFramework.Signal in '..\..\sources\MVCFramework.Signal.pas', MVCFramework.Serializer.Text in '..\..\sources\MVCFramework.Serializer.Text.pas', - MVCFramework.Container in '..\..\sources\MVCFramework.Container.pas'; + MVCFramework.Container in '..\..\sources\MVCFramework.Container.pas', + sqids in '..\..\sources\sqids.pas'; end. diff --git a/packages/d120/dmvcframeworkRT.dproj b/packages/d120/dmvcframeworkRT.dproj index a75654da..c8c3b688 100644 --- a/packages/d120/dmvcframeworkRT.dproj +++ b/packages/d120/dmvcframeworkRT.dproj @@ -221,6 +221,7 @@ + Base diff --git a/samples/serversentevents/SSESample.dpr b/samples/serversentevents/SSESample.dpr index 7a90f660..3d83b58b 100644 --- a/samples/serversentevents/SSESample.dpr +++ b/samples/serversentevents/SSESample.dpr @@ -26,8 +26,8 @@ procedure RunServer(APort: Integer); var LServer: TIdHTTPWebBrokerBridge; begin - Writeln('** DMVCFramework Server ** build ' + DMVCFRAMEWORK_VERSION); - Writeln(Format('Starting HTTP Server on port %d', [APort])); + LogI('** DMVCFramework Server ** build ' + DMVCFRAMEWORK_VERSION); + LogI(Format('Starting HTTP Server on port %d', [APort])); LServer := TIdHTTPWebBrokerBridge.Create(nil); try LServer.KeepAlive := True; @@ -43,7 +43,7 @@ begin {$IFDEF MSWINDOWS} ShellExecute(0, 'open', PChar('http://localhost:' + inttostr(APort) + '/static'), nil, nil, SW_SHOWMAXIMIZED); {$ENDIF} - Write('CTRL+C to stop the server'); + LogI('CTRL+C to stop the server'); WaitForTerminationSignal; EnterInShutdownState; finally @@ -54,6 +54,7 @@ end; begin ReportMemoryLeaksOnShutdown := True; IsMultiThread := True; + UseConsoleLogger := True; try if WebRequestHandler <> nil then WebRequestHandler.WebModuleClass := WebModuleClass; @@ -61,7 +62,7 @@ begin RunServer(8080); except on E: Exception do - Writeln(E.ClassName, ': ', E.Message); + LogE(E.ClassName + ': ' + E.Message); end; end. diff --git a/samples/serversentevents/SSESample.dproj b/samples/serversentevents/SSESample.dproj index 467e4faf..040dfb24 100644 --- a/samples/serversentevents/SSESample.dproj +++ b/samples/serversentevents/SSESample.dproj @@ -1,7 +1,7 @@  {56928A09-5B7B-4920-ABAA-CB68F0AC2958} - 19.5 + 20.1 VCL SSESample.dpr True @@ -9,6 +9,7 @@ Win32 1 Console + SSESample true @@ -146,8 +147,8 @@ - + @@ -237,6 +238,16 @@ 1 + + + res\drawable-anydpi-v21 + 1 + + + res\drawable-anydpi-v21 + 1 + + res\values @@ -257,6 +268,66 @@ 1 + + + res\values-v31 + 1 + + + res\values-v31 + 1 + + + + + res\drawable-anydpi-v26 + 1 + + + res\drawable-anydpi-v26 + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\drawable-anydpi-v33 + 1 + + + res\drawable-anydpi-v33 + 1 + + res\values @@ -267,6 +338,16 @@ 1 + + + res\values-night-v21 + 1 + + + res\values-night-v21 + 1 + + res\drawable @@ -437,6 +518,56 @@ 1 + + + res\drawable-anydpi-v24 + 1 + + + res\drawable-anydpi-v24 + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\drawable-night-anydpi-v21 + 1 + + + res\drawable-night-anydpi-v21 + 1 + + + + + res\drawable-anydpi-v31 + 1 + + + res\drawable-anydpi-v31 + 1 + + + + + res\drawable-night-anydpi-v31 + 1 + + + res\drawable-night-anydpi-v31 + 1 + + 1 @@ -542,6 +673,130 @@ 0 + + + 1 + + + 1 + + + + + + + + Contents\Resources + 1 + + + Contents\Resources + 1 + + + Contents\Resources + 1 + + + + + library\lib\armeabi-v7a + 1 + + + library\lib\arm64-v8a + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 0 + + + + + library\lib\armeabi-v7a + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + + + + + + 1 + + + 1 + + + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + Assets + 1 + + + Assets + 1 + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset @@ -742,127 +997,6 @@ 1 - - - 1 - - - 1 - - - - - ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF - 1 - - - ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF - 1 - - - ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF - 1 - - - - - - - - 1 - - - 1 - - - 1 - - - - - - - - Contents\Resources - 1 - - - Contents\Resources - 1 - - - Contents\Resources - 1 - - - - - library\lib\armeabi-v7a - 1 - - - library\lib\arm64-v8a - 1 - - - 1 - - - 1 - - - 1 - - - 1 - - - 1 - - - 1 - - - 1 - - - 0 - - - - - library\lib\armeabi-v7a - 1 - - - - - 1 - - - 1 - - - - - Assets - 1 - - - Assets - 1 - - - - - Assets - 1 - - - Assets - 1 - - @@ -875,6 +1009,7 @@ + False diff --git a/samples/serversentevents2/WebModuleU.dfm b/samples/serversentevents2/WebModuleU.dfm index 018d0670..02d66b97 100644 --- a/samples/serversentevents2/WebModuleU.dfm +++ b/samples/serversentevents2/WebModuleU.dfm @@ -1,5 +1,4 @@ object MyWebModule: TMyWebModule - OldCreateOrder = False OnCreate = WebModuleCreate OnDestroy = WebModuleDestroy Actions = <> diff --git a/samples/serversentevents2/WebModuleU.pas b/samples/serversentevents2/WebModuleU.pas index dcbe6ca3..7c5bbde3 100644 --- a/samples/serversentevents2/WebModuleU.pas +++ b/samples/serversentevents2/WebModuleU.pas @@ -1,68 +1,69 @@ unit WebModuleU; - -interface - -uses System.SysUtils, - System.Classes, - Web.HTTPApp, - MVCFramework; - -type - TMyWebModule = class(TWebModule) - procedure WebModuleCreate(Sender: TObject); - procedure WebModuleDestroy(Sender: TObject); - private - FMVC: TMVCEngine; - public - { Public declarations } - end; - -var - WebModuleClass: TComponentClass = TMyWebModule; - -implementation - -{$R *.dfm} - -uses - MVCFramework.Commons, - MVCFramework.Middleware.StaticFiles, - StatusControllerU; - -procedure TMyWebModule.WebModuleCreate(Sender: TObject); -begin - FMVC := TMVCEngine.Create(Self, - procedure(Config: TMVCConfig) - begin - // session timeout (0 means session cookie) - Config[TMVCConfigKey.SessionTimeout] := '0'; - // default content-type - Config[TMVCConfigKey.DefaultContentType] := - TMVCConstants.DEFAULT_CONTENT_TYPE; - // default content charset - Config[TMVCConfigKey.DefaultContentCharset] := - TMVCConstants.DEFAULT_CONTENT_CHARSET; - // unhandled actions are permitted? - Config[TMVCConfigKey.AllowUnhandledAction] := 'false'; - // default view file extension - Config[TMVCConfigKey.DefaultViewFileExtension] := 'html'; - // view path - Config[TMVCConfigKey.ViewPath] := 'templates'; - // Enable Server Signature in response - Config[TMVCConfigKey.ExposeServerSignature] := 'true'; - end); - FMVC.AddController(TStatusController); - FMVC.AddMiddleware(TMVCStaticFilesMiddleware.Create( - '/static', { StaticFilesPath } - ExtractFilePath(GetModuleName(HInstance)) + '\www', { DocumentRoot } - 'index.html' {IndexDocument - Before it was named fallbackresource} - )); -end; - -procedure TMyWebModule.WebModuleDestroy(Sender: TObject); -begin - FMVC.Free; -end; - + + +interface + +uses System.SysUtils, + System.Classes, + Web.HTTPApp, + MVCFramework; + +type + TMyWebModule = class(TWebModule) + procedure WebModuleCreate(Sender: TObject); + procedure WebModuleDestroy(Sender: TObject); + private + FMVC: TMVCEngine; + public + { Public declarations } + end; + +var + WebModuleClass: TComponentClass = TMyWebModule; + +implementation + +{$R *.dfm} + +uses + MVCFramework.Commons, + MVCFramework.Middleware.StaticFiles, + StatusControllerU; + +procedure TMyWebModule.WebModuleCreate(Sender: TObject); +begin + FMVC := TMVCEngine.Create(Self, + procedure(Config: TMVCConfig) + begin + // session timeout (0 means session cookie) + Config[TMVCConfigKey.SessionTimeout] := '0'; + // default content-type + Config[TMVCConfigKey.DefaultContentType] := + TMVCConstants.DEFAULT_CONTENT_TYPE; + // default content charset + Config[TMVCConfigKey.DefaultContentCharset] := + TMVCConstants.DEFAULT_CONTENT_CHARSET; + // unhandled actions are permitted? + Config[TMVCConfigKey.AllowUnhandledAction] := 'false'; + // default view file extension + Config[TMVCConfigKey.DefaultViewFileExtension] := 'html'; + // view path + Config[TMVCConfigKey.ViewPath] := 'templates'; + // Enable Server Signature in response + Config[TMVCConfigKey.ExposeServerSignature] := 'true'; + end); + FMVC.AddController(TStatusController); + FMVC.AddMiddleware(TMVCStaticFilesMiddleware.Create( + '/static', { StaticFilesPath } + ExtractFilePath(GetModuleName(HInstance)) + '\www', { DocumentRoot } + 'index.html' {IndexDocument - Before it was named fallbackresource} + )); +end; + +procedure TMyWebModule.WebModuleDestroy(Sender: TObject); +begin + FMVC.Free; +end; + end. - \ No newline at end of file + diff --git a/samples/serversentevents2/serversentevents2.dproj b/samples/serversentevents2/serversentevents2.dproj index c3e0eff7..e30e6826 100644 --- a/samples/serversentevents2/serversentevents2.dproj +++ b/samples/serversentevents2/serversentevents2.dproj @@ -1,7 +1,7 @@  {FD2E10E8-0B57-424F-BB4B-827E8087AC33} - 19.5 + 20.1 VCL serversentevents2.dpr True @@ -9,6 +9,7 @@ Win32 1 Console + serversentevents2 true @@ -143,8 +144,8 @@ - + @@ -234,6 +235,16 @@ 1 + + + res\drawable-anydpi-v21 + 1 + + + res\drawable-anydpi-v21 + 1 + + res\values @@ -254,6 +265,66 @@ 1 + + + res\values-v31 + 1 + + + res\values-v31 + 1 + + + + + res\drawable-anydpi-v26 + 1 + + + res\drawable-anydpi-v26 + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\drawable-anydpi-v33 + 1 + + + res\drawable-anydpi-v33 + 1 + + res\values @@ -264,6 +335,16 @@ 1 + + + res\values-night-v21 + 1 + + + res\values-night-v21 + 1 + + res\drawable @@ -434,6 +515,56 @@ 1 + + + res\drawable-anydpi-v24 + 1 + + + res\drawable-anydpi-v24 + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\drawable-night-anydpi-v21 + 1 + + + res\drawable-night-anydpi-v21 + 1 + + + + + res\drawable-anydpi-v31 + 1 + + + res\drawable-anydpi-v31 + 1 + + + + + res\drawable-night-anydpi-v31 + 1 + + + res\drawable-night-anydpi-v31 + 1 + + 1 @@ -539,6 +670,130 @@ 0 + + + 1 + + + 1 + + + + + + + + Contents\Resources + 1 + + + Contents\Resources + 1 + + + Contents\Resources + 1 + + + + + library\lib\armeabi-v7a + 1 + + + library\lib\arm64-v8a + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 0 + + + + + library\lib\armeabi-v7a + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + + + + + + 1 + + + 1 + + + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + Assets + 1 + + + Assets + 1 + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset @@ -739,127 +994,6 @@ 1 - - - 1 - - - 1 - - - - - ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF - 1 - - - ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF - 1 - - - ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF - 1 - - - - - - - - 1 - - - 1 - - - 1 - - - - - - - - Contents\Resources - 1 - - - Contents\Resources - 1 - - - Contents\Resources - 1 - - - - - library\lib\armeabi-v7a - 1 - - - library\lib\arm64-v8a - 1 - - - 1 - - - 1 - - - 1 - - - 1 - - - 1 - - - 1 - - - 1 - - - 0 - - - - - library\lib\armeabi-v7a - 1 - - - - - 1 - - - 1 - - - - - Assets - 1 - - - Assets - 1 - - - - - Assets - 1 - - - Assets - 1 - - @@ -872,6 +1006,7 @@ + False diff --git a/samples/serversentevents2/serversentevents2sender.dproj b/samples/serversentevents2/serversentevents2sender.dproj index 2dddb82d..2a070958 100644 --- a/samples/serversentevents2/serversentevents2sender.dproj +++ b/samples/serversentevents2/serversentevents2sender.dproj @@ -1,7 +1,7 @@  {08D68152-4263-463F-A029-75E58C0AAD41} - 19.5 + 20.1 VCL serversentevents2sender.dpr True @@ -9,6 +9,7 @@ Win32 1 Application + serversentevents2sender true @@ -226,6 +227,16 @@ 1 + + + res\drawable-anydpi-v21 + 1 + + + res\drawable-anydpi-v21 + 1 + + res\values @@ -246,6 +257,66 @@ 1 + + + res\values-v31 + 1 + + + res\values-v31 + 1 + + + + + res\drawable-anydpi-v26 + 1 + + + res\drawable-anydpi-v26 + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\drawable-anydpi-v33 + 1 + + + res\drawable-anydpi-v33 + 1 + + res\values @@ -256,6 +327,16 @@ 1 + + + res\values-night-v21 + 1 + + + res\values-night-v21 + 1 + + res\drawable @@ -426,6 +507,56 @@ 1 + + + res\drawable-anydpi-v24 + 1 + + + res\drawable-anydpi-v24 + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\drawable-night-anydpi-v21 + 1 + + + res\drawable-night-anydpi-v21 + 1 + + + + + res\drawable-anydpi-v31 + 1 + + + res\drawable-anydpi-v31 + 1 + + + + + res\drawable-night-anydpi-v31 + 1 + + + res\drawable-night-anydpi-v31 + 1 + + 1 @@ -556,6 +687,200 @@ 0 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + + + ..\ + 1 + + + ..\ + 1 + + + ..\ + 1 + + + + + Contents + 1 + + + Contents + 1 + + + Contents + 1 + + + + + Contents\Resources + 1 + + + Contents\Resources + 1 + + + Contents\Resources + 1 + + + + + library\lib\armeabi-v7a + 1 + + + library\lib\arm64-v8a + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + Contents\MacOS + 1 + + + Contents\MacOS + 1 + + + Contents\MacOS + 1 + + + 0 + + + + + library\lib\armeabi-v7a + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + + + ..\ + 1 + + + ..\ + 1 + + + ..\ + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).launchscreen + 64 + + + ..\$(PROJECTNAME).launchscreen + 64 + + + + + 1 + + + 1 + + + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + Assets + 1 + + + Assets + 1 + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset @@ -756,197 +1081,6 @@ 1 - - - 1 - - - 1 - - - - - ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF - 1 - - - ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF - 1 - - - ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF - 1 - - - - - ..\ - 1 - - - ..\ - 1 - - - ..\ - 1 - - - - - 1 - - - 1 - - - 1 - - - - - ..\$(PROJECTNAME).launchscreen - 64 - - - ..\$(PROJECTNAME).launchscreen - 64 - - - - - 1 - - - 1 - - - 1 - - - - - ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF - 1 - - - ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF - 1 - - - - - ..\ - 1 - - - ..\ - 1 - - - ..\ - 1 - - - - - Contents - 1 - - - Contents - 1 - - - Contents - 1 - - - - - Contents\Resources - 1 - - - Contents\Resources - 1 - - - Contents\Resources - 1 - - - - - library\lib\armeabi-v7a - 1 - - - library\lib\arm64-v8a - 1 - - - 1 - - - 1 - - - 1 - - - 1 - - - Contents\MacOS - 1 - - - Contents\MacOS - 1 - - - Contents\MacOS - 1 - - - 0 - - - - - library\lib\armeabi-v7a - 1 - - - - - 1 - - - 1 - - - - - Assets - 1 - - - Assets - 1 - - - - - Assets - 1 - - - Assets - 1 - - @@ -959,6 +1093,7 @@ + True diff --git a/samples/serversentevents2/serversentevents2viewer.dproj b/samples/serversentevents2/serversentevents2viewer.dproj index e7625110..efc9f63d 100644 --- a/samples/serversentevents2/serversentevents2viewer.dproj +++ b/samples/serversentevents2/serversentevents2viewer.dproj @@ -1,7 +1,7 @@  {4185FCEE-B4FE-490C-8E3A-E2E8F2953663} - 19.5 + 20.1 VCL serversentevents2viewer.dpr True @@ -9,6 +9,7 @@ Win32 1 Application + serversentevents2viewer true @@ -226,6 +227,16 @@ 1 + + + res\drawable-anydpi-v21 + 1 + + + res\drawable-anydpi-v21 + 1 + + res\values @@ -246,6 +257,66 @@ 1 + + + res\values-v31 + 1 + + + res\values-v31 + 1 + + + + + res\drawable-anydpi-v26 + 1 + + + res\drawable-anydpi-v26 + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\drawable-anydpi-v33 + 1 + + + res\drawable-anydpi-v33 + 1 + + res\values @@ -256,6 +327,16 @@ 1 + + + res\values-night-v21 + 1 + + + res\values-night-v21 + 1 + + res\drawable @@ -426,6 +507,56 @@ 1 + + + res\drawable-anydpi-v24 + 1 + + + res\drawable-anydpi-v24 + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\drawable-night-anydpi-v21 + 1 + + + res\drawable-night-anydpi-v21 + 1 + + + + + res\drawable-anydpi-v31 + 1 + + + res\drawable-anydpi-v31 + 1 + + + + + res\drawable-night-anydpi-v31 + 1 + + + res\drawable-night-anydpi-v31 + 1 + + 1 @@ -556,6 +687,200 @@ 0 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + + + ..\ + 1 + + + ..\ + 1 + + + ..\ + 1 + + + + + Contents + 1 + + + Contents + 1 + + + Contents + 1 + + + + + Contents\Resources + 1 + + + Contents\Resources + 1 + + + Contents\Resources + 1 + + + + + library\lib\armeabi-v7a + 1 + + + library\lib\arm64-v8a + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + Contents\MacOS + 1 + + + Contents\MacOS + 1 + + + Contents\MacOS + 1 + + + 0 + + + + + library\lib\armeabi-v7a + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + + + ..\ + 1 + + + ..\ + 1 + + + ..\ + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).launchscreen + 64 + + + ..\$(PROJECTNAME).launchscreen + 64 + + + + + 1 + + + 1 + + + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + Assets + 1 + + + Assets + 1 + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset @@ -756,197 +1081,6 @@ 1 - - - 1 - - - 1 - - - - - ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF - 1 - - - ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF - 1 - - - ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF - 1 - - - - - ..\ - 1 - - - ..\ - 1 - - - ..\ - 1 - - - - - 1 - - - 1 - - - 1 - - - - - ..\$(PROJECTNAME).launchscreen - 64 - - - ..\$(PROJECTNAME).launchscreen - 64 - - - - - 1 - - - 1 - - - 1 - - - - - ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF - 1 - - - ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF - 1 - - - - - ..\ - 1 - - - ..\ - 1 - - - ..\ - 1 - - - - - Contents - 1 - - - Contents - 1 - - - Contents - 1 - - - - - Contents\Resources - 1 - - - Contents\Resources - 1 - - - Contents\Resources - 1 - - - - - library\lib\armeabi-v7a - 1 - - - library\lib\arm64-v8a - 1 - - - 1 - - - 1 - - - 1 - - - 1 - - - Contents\MacOS - 1 - - - Contents\MacOS - 1 - - - Contents\MacOS - 1 - - - 0 - - - - - library\lib\armeabi-v7a - 1 - - - - - 1 - - - 1 - - - - - Assets - 1 - - - Assets - 1 - - - - - Assets - 1 - - - Assets - 1 - - @@ -959,6 +1093,7 @@ + True diff --git a/samples/sqids/ControllerU.pas b/samples/sqids/ControllerU.pas new file mode 100644 index 00000000..77a78b5d --- /dev/null +++ b/samples/sqids/ControllerU.pas @@ -0,0 +1,90 @@ +unit ControllerU; + +interface + +uses + MVCFramework, MVCFramework.Commons, MVCFramework.Nullables, MVCFramework.Serializer.Commons, System.Generics.Collections, + ServicesU, EntityU; + +type + [MVCPath('/api')] + TMyController = class(TMVCController) + public + [MVCPath] + [MVCHTTPMethod([httpGET])] + function Index: String; + + [MVCPath('/reversedstrings/($Value)')] + [MVCHTTPMethod([httpGET])] + [MVCProduces(TMVCMediaType.TEXT_PLAIN)] + function GetReversedString(const Value: String): String; + + //Sample CRUD Actions for a "People" entity + [MVCPath('/people')] + [MVCHTTPMethod([httpGET])] + function GetPeople([MVCInject] PeopleService: IPeopleService): IMVCResponse; + + [MVCPath('/people/($ID:sqid)')] + [MVCHTTPMethod([httpGET])] + function GetPerson(ID: Integer): TPerson; + + [MVCPath('/people')] + [MVCHTTPMethod([httpPOST])] + function CreatePerson([MVCFromBody] Person: TPerson): IMVCResponse; + + [MVCPath('/people/($ID)')] + [MVCHTTPMethod([httpPUT])] + function UpdatePerson(ID: Integer; [MVCFromBody] Person: TPerson): IMVCResponse; + + [MVCPath('/people/($ID)')] + [MVCHTTPMethod([httpDELETE])] + function DeletePerson(ID: Integer): IMVCResponse; + end; + +implementation + +uses + System.StrUtils, System.SysUtils, MVCFramework.Logger; + + +function TMyController.Index: String; +begin + //use Context property to access to the HTTP request and response + Result := 'Hello DelphiMVCFramework World'; +end; + +function TMyController.GetReversedString(const Value: String): String; +begin + Result := System.StrUtils.ReverseString(Value.Trim); +end; + +//Sample CRUD Actions for a "People" entity (with service injection) +function TMyController.GetPeople(PeopleService: IPeopleService): IMVCResponse; +begin + Result := OkResponse(PeopleService.GetAll); +end; + +function TMyController.GetPerson(ID: Integer): TPerson; +begin + Result := TPerson.Create(ID, 'Daniele', 'Teti', EncodeDate(1979, 11, 4)); +end; + +function TMyController.CreatePerson([MVCFromBody] Person: TPerson): IMVCResponse; +begin + LogI('Created ' + Person.FirstName + ' ' + Person.LastName); + Result := CreatedResponse('', 'Person created'); +end; + +function TMyController.UpdatePerson(ID: Integer; [MVCFromBody] Person: TPerson): IMVCResponse; +begin + LogI('Updated ' + Person.FirstName + ' ' + Person.LastName); + Result := NoContentResponse(); +end; + +function TMyController.DeletePerson(ID: Integer): IMVCResponse; +begin + LogI('Deleted person with id ' + ID.ToString); + Result := NoContentResponse(); +end; + +end. diff --git a/samples/sqids/EntityU.pas b/samples/sqids/EntityU.pas new file mode 100644 index 00000000..d86eee73 --- /dev/null +++ b/samples/sqids/EntityU.pas @@ -0,0 +1,36 @@ +unit EntityU; + +interface + +uses + MVCFramework.Nullables, MVCFramework.Serializer.Commons; + +type + [MVCNameCase(ncCamelCase)] + TPerson = class + private + fID: NullableInt32; + fFirstName: String; + fLastName: String; + fDOB: TDate; + public + property ID: NullableInt32 read fID write fID; + property FirstName: String read fFirstName write fFirstName; + property LastName: String read fLastName write fLastName; + property DOB: TDate read fDOB write fDOB; + constructor Create(ID: Integer; FirstName, LastName: String; DOB: TDate); + end; + +implementation + +constructor TPerson.Create(ID: Integer; FirstName, LastName: String; DOB: TDate); +begin + inherited Create; + fID := ID; + fFirstName := FirstName; + fLastName := LastName; + fDOB := DOB; +end; + + +end. diff --git a/samples/sqids/ServicesU.pas b/samples/sqids/ServicesU.pas new file mode 100644 index 00000000..0b856d06 --- /dev/null +++ b/samples/sqids/ServicesU.pas @@ -0,0 +1,44 @@ +unit ServicesU; + +interface + +uses + MVCFramework.Container, System.Generics.Collections, EntityU; + +type + IPeopleService = interface + ['{0198920C-4CD1-4E90-ABEA-B86B5C36FF36}'] + function GetAll: TObjectList; + end; + + TPeopleService = class(TInterfacedObject, IPeopleService) + protected + function GetAll: TObjectList; + end; + +procedure RegisterServices(Container: IMVCServiceContainer); + +implementation + +uses + System.SysUtils; + +procedure RegisterServices(Container: IMVCServiceContainer); +begin + Container.RegisterType(TPeopleService, IPeopleService, TRegistrationType.SingletonPerRequest); + // Register other services here +end; + +function TPeopleService.GetAll: TObjectList; +begin + Result := TObjectList.Create; + Result.AddRange([ + TPerson.Create(1, 'Henry', 'Ford', EncodeDate(1863, 7, 30)), + TPerson.Create(2, 'Guglielmo', 'Marconi', EncodeDate(1874, 4, 25)), + TPerson.Create(3, 'Antonio', 'Meucci', EncodeDate(1808, 4, 13)), + TPerson.Create(4, 'Michael', 'Faraday', EncodeDate(1867, 9, 22)) + ]); +end; + + +end. diff --git a/samples/sqids/WebModuleU.dfm b/samples/sqids/WebModuleU.dfm new file mode 100644 index 00000000..02d66b97 --- /dev/null +++ b/samples/sqids/WebModuleU.dfm @@ -0,0 +1,7 @@ +object MyWebModule: TMyWebModule + OnCreate = WebModuleCreate + OnDestroy = WebModuleDestroy + Actions = <> + Height = 230 + Width = 415 +end diff --git a/samples/sqids/WebModuleU.pas b/samples/sqids/WebModuleU.pas new file mode 100644 index 00000000..ab0d2517 --- /dev/null +++ b/samples/sqids/WebModuleU.pas @@ -0,0 +1,82 @@ +unit WebModuleU; + +interface + +uses + System.SysUtils, + System.Classes, + Web.HTTPApp, + MVCFramework; + +type + TMyWebModule = class(TWebModule) + procedure WebModuleCreate(Sender: TObject); + procedure WebModuleDestroy(Sender: TObject); + private + fMVC: TMVCEngine; + end; + +var + WebModuleClass: TComponentClass = TMyWebModule; + +implementation + +{$R *.dfm} + +uses + System.IOUtils, + MVCFramework.Commons, + MVCFramework.Middleware.ActiveRecord, + MVCFramework.Middleware.StaticFiles, + MVCFramework.Middleware.Analytics, + MVCFramework.Middleware.Trace, + MVCFramework.Middleware.CORS, + MVCFramework.Middleware.ETag, + MVCFramework.Middleware.Compression, ControllerU; + +procedure TMyWebModule.WebModuleCreate(Sender: TObject); +begin + FMVC := TMVCEngine.Create(Self, + procedure(Config: TMVCConfig) + begin + // session timeout (0 means session cookie) + Config[TMVCConfigKey.SessionTimeout] := dotEnv.Env('dmvc.session_timeout', '0'); + //default content-type + Config[TMVCConfigKey.DefaultContentType] := dotEnv.Env('dmvc.default.content_type', TMVCConstants.DEFAULT_CONTENT_TYPE); + //default content charset + Config[TMVCConfigKey.DefaultContentCharset] := dotEnv.Env('dmvc.default.content_charset', TMVCConstants.DEFAULT_CONTENT_CHARSET); + //unhandled actions are permitted? + Config[TMVCConfigKey.AllowUnhandledAction] := dotEnv.Env('dmvc.allow_unhandled_actions', 'false'); + //enables or not system controllers loading (available only from localhost requests) + Config[TMVCConfigKey.LoadSystemControllers] := dotEnv.Env('dmvc.load_system_controllers', 'true'); + //default view file extension + Config[TMVCConfigKey.DefaultViewFileExtension] := dotEnv.Env('dmvc.default.view_file_extension', 'html'); + //view path + Config[TMVCConfigKey.ViewPath] := dotEnv.Env('dmvc.view_path', 'templates'); + //use cache for server side views (use "false" in debug and "true" in production for faster performances + Config[TMVCConfigKey.ViewCache] := dotEnv.Env('dmvc.view_cache', 'false'); + //Max Record Count for automatic Entities CRUD + Config[TMVCConfigKey.MaxEntitiesRecordCount] := dotEnv.Env('dmvc.max_entities_record_count', IntToStr(TMVCConstants.MAX_RECORD_COUNT)); + //Enable Server Signature in response + Config[TMVCConfigKey.ExposeServerSignature] := dotEnv.Env('dmvc.expose_server_signature', 'false'); + //Enable X-Powered-By Header in response + Config[TMVCConfigKey.ExposeXPoweredBy] := dotEnv.Env('dmvc.expose_x_powered_by', 'true'); + // Max request size in bytes + Config[TMVCConfigKey.MaxRequestSize] := dotEnv.Env('dmvc.max_request_size', IntToStr(TMVCConstants.DEFAULT_MAX_REQUEST_SIZE)); + end); + + // Controllers + FMVC.AddController(TMyController); + // Controllers - END + + // Middleware + // Middleware - END + +end; + +procedure TMyWebModule.WebModuleDestroy(Sender: TObject); +begin + FMVC.Free; +end; + +end. diff --git a/samples/sqids/sqids_sample.dpr b/samples/sqids/sqids_sample.dpr new file mode 100644 index 00000000..6e922a19 --- /dev/null +++ b/samples/sqids/sqids_sample.dpr @@ -0,0 +1,80 @@ +program sqids_sample; + +{$APPTYPE CONSOLE} + +uses + System.SysUtils, + Web.ReqMulti, + Web.WebReq, + Web.WebBroker, + IdContext, + IdHTTPWebBrokerBridge, + MVCFramework, + MVCFramework.Logger, + MVCFramework.DotEnv, + MVCFramework.Commons, + MVCFramework.Container, + MVCFramework.Signal, + EntityU in 'EntityU.pas', + ServicesU in 'ServicesU.pas', + ControllerU in 'ControllerU.pas', + WebModuleU in 'WebModuleU.pas' {MyWebModule: TWebModule}; + +{$R *.res} + +procedure RunServer(APort: Integer); +var + LServer: TIdHTTPWebBrokerBridge; +begin + LServer := TIdHTTPWebBrokerBridge.Create(nil); + try + LServer.OnParseAuthentication := TMVCParseAuthentication.OnParseAuthentication; + LServer.DefaultPort := APort; + LServer.KeepAlive := dotEnv.Env('dmvc.indy.keep_alive', True); + LServer.MaxConnections := dotEnv.Env('dmvc.webbroker.max_connections', 0); + LServer.ListenQueue := dotEnv.Env('dmvc.indy.listen_queue', 500); + LServer.Active := True; + LogI('Listening on port ' + APort.ToString); + LogI('Application started. Press Ctrl+C to shut down.'); + WaitForTerminationSignal; + EnterInShutdownState; + LServer.Active := False; + finally + LServer.Free; + end; +end; + +begin + { Enable ReportMemoryLeaksOnShutdown during debug } + // ReportMemoryLeaksOnShutdown := True; + IsMultiThread := True; + // DMVCFramework Specific Configuration + // When MVCSerializeNulls = True empty nullables and nil are serialized as json null. + // When MVCSerializeNulls = False empty nullables and nil are not serialized at all. + MVCSerializeNulls := True; + UseConsoleLogger := True; + + LogI('** DMVCFramework Server ** build ' + DMVCFRAMEWORK_VERSION); + try + if WebRequestHandler <> nil then + WebRequestHandler.WebModuleClass := WebModuleClass; + + WebRequestHandlerProc.MaxConnections := dotEnv.Env('dmvc.handler.max_connections', 1024); + +{$IF CompilerVersion >= 34} //SYDNEY+ + if dotEnv.Env('dmvc.profiler.enabled', false) then + begin + Profiler.ProfileLogger := Log; + Profiler.WarningThreshold := dotEnv.Env('dmvc.profiler.warning_threshold', 2000); + end; +{$ENDIF} + + RegisterServices(DefaultMVCServiceContainer); + DefaultMVCServiceContainer.Build; + + RunServer(dotEnv.Env('dmvc.server.port', 8080)); + except + on E: Exception do + LogF(E.ClassName + ': ' + E.Message); + end; +end. diff --git a/samples/sqids/sqids_sample.dproj b/samples/sqids/sqids_sample.dproj new file mode 100644 index 00000000..4e70247b --- /dev/null +++ b/samples/sqids/sqids_sample.dproj @@ -0,0 +1,1034 @@ + + + {97706A56-7C5B-4D6E-A3C1-FA100BF54535} + 20.1 + None + True + Debug + Win32 + sqids_sample + 1 + Console + sqids_sample.dpr + + + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Cfg_1 + true + true + + + true + Base + true + + + .\$(Platform)\$(Config) + .\$(Platform)\$(Config) + false + false + false + false + false + System;Xml;Data;Datasnap;Web;Soap;$(DCC_Namespace) + true + $(BDS)\bin\delphi_PROJECTICON.ico + $(BDS)\bin\delphi_PROJECTICNS.icns + $(DMVC);$(DCC_UnitSearchPath) + FMX;$(DCC_Framework) + sqids_sample + + + fmx;DbxCommonDriver;bindengine;IndyIPCommon;emsclient;FireDACCommonDriver;IndyProtocols;IndyIPClient;dbxcds;bindcompfmx;FireDACSqliteDriver;DbxClientDriver;soapmidas;fmxFireDAC;dbexpress;inet;DataSnapCommon;fmxase;dbrtl;FireDACDBXDriver;CustomIPTransport;DBXInterBaseDriver;IndySystem;bindcomp;FireDACCommon;IndyCore;RESTBackendComponents;bindcompdbx;rtl;RESTComponents;DBXSqliteDriver;IndyIPServer;dsnapxml;DataSnapClient;DataSnapProviderClient;DataSnapFireDAC;emsclientfiredac;FireDAC;FireDACDSDriver;xmlrtl;tethering;dsnap;CloudService;DataSnapNativeClient;soaprtl;soapserver;FireDACIBDriver;$(DCC_UsePackage) + activity-1.7.2.dex.jar;annotation-experimental-1.3.0.dex.jar;annotation-jvm-1.6.0.dex.jar;annotations-13.0.dex.jar;appcompat-1.2.0.dex.jar;appcompat-resources-1.2.0.dex.jar;billing-6.0.1.dex.jar;biometric-1.1.0.dex.jar;browser-1.4.0.dex.jar;cloud-messaging.dex.jar;collection-1.1.0.dex.jar;concurrent-futures-1.1.0.dex.jar;core-1.10.1.dex.jar;core-common-2.2.0.dex.jar;core-ktx-1.10.1.dex.jar;core-runtime-2.2.0.dex.jar;cursoradapter-1.0.0.dex.jar;customview-1.0.0.dex.jar;documentfile-1.0.0.dex.jar;drawerlayout-1.0.0.dex.jar;error_prone_annotations-2.9.0.dex.jar;exifinterface-1.3.6.dex.jar;firebase-annotations-16.2.0.dex.jar;firebase-common-20.3.1.dex.jar;firebase-components-17.1.0.dex.jar;firebase-datatransport-18.1.7.dex.jar;firebase-encoders-17.0.0.dex.jar;firebase-encoders-json-18.0.0.dex.jar;firebase-encoders-proto-16.0.0.dex.jar;firebase-iid-interop-17.1.0.dex.jar;firebase-installations-17.1.3.dex.jar;firebase-installations-interop-17.1.0.dex.jar;firebase-measurement-connector-19.0.0.dex.jar;firebase-messaging-23.1.2.dex.jar;fmx.dex.jar;fragment-1.2.5.dex.jar;google-play-licensing.dex.jar;interpolator-1.0.0.dex.jar;javax.inject-1.dex.jar;kotlin-stdlib-1.8.22.dex.jar;kotlin-stdlib-common-1.8.22.dex.jar;kotlin-stdlib-jdk7-1.8.22.dex.jar;kotlin-stdlib-jdk8-1.8.22.dex.jar;kotlinx-coroutines-android-1.6.4.dex.jar;kotlinx-coroutines-core-jvm-1.6.4.dex.jar;legacy-support-core-utils-1.0.0.dex.jar;lifecycle-common-2.6.1.dex.jar;lifecycle-livedata-2.6.1.dex.jar;lifecycle-livedata-core-2.6.1.dex.jar;lifecycle-runtime-2.6.1.dex.jar;lifecycle-service-2.6.1.dex.jar;lifecycle-viewmodel-2.6.1.dex.jar;lifecycle-viewmodel-savedstate-2.6.1.dex.jar;listenablefuture-1.0.dex.jar;loader-1.0.0.dex.jar;localbroadcastmanager-1.0.0.dex.jar;okio-jvm-3.4.0.dex.jar;play-services-ads-22.2.0.dex.jar;play-services-ads-base-22.2.0.dex.jar;play-services-ads-identifier-18.0.0.dex.jar;play-services-ads-lite-22.2.0.dex.jar;play-services-appset-16.0.1.dex.jar;play-services-base-18.1.0.dex.jar;play-services-basement-18.1.0.dex.jar;play-services-cloud-messaging-17.0.1.dex.jar;play-services-location-21.0.1.dex.jar;play-services-maps-18.1.0.dex.jar;play-services-measurement-base-20.1.2.dex.jar;play-services-measurement-sdk-api-20.1.2.dex.jar;play-services-stats-17.0.2.dex.jar;play-services-tasks-18.0.2.dex.jar;print-1.0.0.dex.jar;profileinstaller-1.3.0.dex.jar;room-common-2.2.5.dex.jar;room-runtime-2.2.5.dex.jar;savedstate-1.2.1.dex.jar;sqlite-2.1.0.dex.jar;sqlite-framework-2.1.0.dex.jar;startup-runtime-1.1.1.dex.jar;tracing-1.0.0.dex.jar;transport-api-3.0.0.dex.jar;transport-backend-cct-3.1.8.dex.jar;transport-runtime-3.1.8.dex.jar;user-messaging-platform-2.0.0.dex.jar;vectordrawable-1.1.0.dex.jar;vectordrawable-animated-1.1.0.dex.jar;versionedparcelable-1.1.1.dex.jar;viewpager-1.0.0.dex.jar;work-runtime-2.7.0.dex.jar + + + fmx;DbxCommonDriver;bindengine;IndyIPCommon;emsclient;FireDACCommonDriver;IndyProtocols;IndyIPClient;dbxcds;bindcompfmx;FireDACSqliteDriver;DbxClientDriver;soapmidas;fmxFireDAC;dbexpress;inet;DataSnapCommon;dbrtl;FireDACDBXDriver;CustomIPTransport;DBXInterBaseDriver;IndySystem;bindcomp;FireDACCommon;IndyCore;RESTBackendComponents;bindcompdbx;rtl;RESTComponents;DBXSqliteDriver;IndyIPServer;dsnapxml;DataSnapClient;DataSnapProviderClient;DataSnapFireDAC;emsclientfiredac;FireDAC;FireDACDSDriver;xmlrtl;tethering;dsnap;CloudService;DataSnapNativeClient;soaprtl;soapserver;FireDACIBDriver;$(DCC_UsePackage) + activity-1.7.2.dex.jar;annotation-experimental-1.3.0.dex.jar;annotation-jvm-1.6.0.dex.jar;annotations-13.0.dex.jar;appcompat-1.2.0.dex.jar;appcompat-resources-1.2.0.dex.jar;billing-6.0.1.dex.jar;biometric-1.1.0.dex.jar;browser-1.4.0.dex.jar;cloud-messaging.dex.jar;collection-1.1.0.dex.jar;concurrent-futures-1.1.0.dex.jar;core-1.10.1.dex.jar;core-common-2.2.0.dex.jar;core-ktx-1.10.1.dex.jar;core-runtime-2.2.0.dex.jar;cursoradapter-1.0.0.dex.jar;customview-1.0.0.dex.jar;documentfile-1.0.0.dex.jar;drawerlayout-1.0.0.dex.jar;error_prone_annotations-2.9.0.dex.jar;exifinterface-1.3.6.dex.jar;firebase-annotations-16.2.0.dex.jar;firebase-common-20.3.1.dex.jar;firebase-components-17.1.0.dex.jar;firebase-datatransport-18.1.7.dex.jar;firebase-encoders-17.0.0.dex.jar;firebase-encoders-json-18.0.0.dex.jar;firebase-encoders-proto-16.0.0.dex.jar;firebase-iid-interop-17.1.0.dex.jar;firebase-installations-17.1.3.dex.jar;firebase-installations-interop-17.1.0.dex.jar;firebase-measurement-connector-19.0.0.dex.jar;firebase-messaging-23.1.2.dex.jar;fmx.dex.jar;fragment-1.2.5.dex.jar;google-play-licensing.dex.jar;interpolator-1.0.0.dex.jar;javax.inject-1.dex.jar;kotlin-stdlib-1.8.22.dex.jar;kotlin-stdlib-common-1.8.22.dex.jar;kotlin-stdlib-jdk7-1.8.22.dex.jar;kotlin-stdlib-jdk8-1.8.22.dex.jar;kotlinx-coroutines-android-1.6.4.dex.jar;kotlinx-coroutines-core-jvm-1.6.4.dex.jar;legacy-support-core-utils-1.0.0.dex.jar;lifecycle-common-2.6.1.dex.jar;lifecycle-livedata-2.6.1.dex.jar;lifecycle-livedata-core-2.6.1.dex.jar;lifecycle-runtime-2.6.1.dex.jar;lifecycle-service-2.6.1.dex.jar;lifecycle-viewmodel-2.6.1.dex.jar;lifecycle-viewmodel-savedstate-2.6.1.dex.jar;listenablefuture-1.0.dex.jar;loader-1.0.0.dex.jar;localbroadcastmanager-1.0.0.dex.jar;okio-jvm-3.4.0.dex.jar;play-services-ads-22.2.0.dex.jar;play-services-ads-base-22.2.0.dex.jar;play-services-ads-identifier-18.0.0.dex.jar;play-services-ads-lite-22.2.0.dex.jar;play-services-appset-16.0.1.dex.jar;play-services-base-18.1.0.dex.jar;play-services-basement-18.1.0.dex.jar;play-services-cloud-messaging-17.0.1.dex.jar;play-services-location-21.0.1.dex.jar;play-services-maps-18.1.0.dex.jar;play-services-measurement-base-20.1.2.dex.jar;play-services-measurement-sdk-api-20.1.2.dex.jar;play-services-stats-17.0.2.dex.jar;play-services-tasks-18.0.2.dex.jar;print-1.0.0.dex.jar;profileinstaller-1.3.0.dex.jar;room-common-2.2.5.dex.jar;room-runtime-2.2.5.dex.jar;savedstate-1.2.1.dex.jar;sqlite-2.1.0.dex.jar;sqlite-framework-2.1.0.dex.jar;startup-runtime-1.1.1.dex.jar;tracing-1.0.0.dex.jar;transport-api-3.0.0.dex.jar;transport-backend-cct-3.1.8.dex.jar;transport-runtime-3.1.8.dex.jar;user-messaging-platform-2.0.0.dex.jar;vectordrawable-1.1.0.dex.jar;vectordrawable-animated-1.1.0.dex.jar;versionedparcelable-1.1.1.dex.jar;viewpager-1.0.0.dex.jar;work-runtime-2.7.0.dex.jar + + + DataSnapServer;fmx;emshosting;DbxCommonDriver;bindengine;FireDACCommonODBC;emsclient;FireDACCommonDriver;IndyProtocols;dbxcds;emsedge;inetdb;FireDACSqliteDriver;DbxClientDriver;FireDACASADriver;soapmidas;dbexpress;FireDACInfxDriver;inet;DataSnapCommon;dbrtl;FireDACOracleDriver;CustomIPTransport;FireDACMSSQLDriver;DataSnapIndy10ServerTransport;DataSnapConnectors;FireDACMongoDBDriver;IndySystem;FireDACTDataDriver;bindcomp;FireDACCommon;DataSnapServerMidas;FireDACODBCDriver;emsserverresource;IndyCore;RESTBackendComponents;rtl;FireDACMySQLDriver;FireDACADSDriver;RESTComponents;dsnapxml;DataSnapClient;DataSnapFireDAC;emsclientfiredac;FireDACPgDriver;FireDAC;xmlrtl;dsnap;CloudService;FireDACDb2Driver;DataSnapNativeClient;DatasnapConnectorsFreePascal;soaprtl;soapserver;FireDACIBDriver;$(DCC_UsePackage) + + + vclwinx;DataSnapServer;fmx;emshosting;vclie;DbxCommonDriver;bindengine;IndyIPCommon;VCLRESTComponents;DBXMSSQLDriver;FireDACCommonODBC;emsclient;FireDACCommonDriver;appanalytics;IndyProtocols;vclx;Skia.Package.RTL;IndyIPClient;dbxcds;vcledge;bindcompvclwinx;emsedge;bindcompfmx;DBXFirebirdDriver;inetdb;FireDACSqliteDriver;DbxClientDriver;FireDACASADriver;soapmidas;vclactnband;fmxFireDAC;dbexpress;FireDACInfxDriver;DBXMySQLDriver;VclSmp;inet;DataSnapCommon;vcltouch;fmxase;DBXOdbcDriver;dbrtl;FireDACDBXDriver;Skia.Package.FMX;FireDACOracleDriver;fmxdae;FireDACMSAccDriver;CustomIPTransport;FireDACMSSQLDriver;DataSnapIndy10ServerTransport;DataSnapConnectors;vcldsnap;DBXInterBaseDriver;FireDACMongoDBDriver;IndySystem;FireDACTDataDriver;Skia.Package.VCL;vcldb;StyledComponents;vclFireDAC;bindcomp;FireDACCommon;DataSnapServerMidas;FireDACODBCDriver;emsserverresource;IndyCore;RESTBackendComponents;dmvcframeworkDT;bindcompdbx;rtl;FireDACMySQLDriver;FireDACADSDriver;StyledAnimatedComponents;RESTComponents;DBXSqliteDriver;vcl;IndyIPServer;dsnapxml;dsnapcon;DataSnapClient;DataSnapProviderClient;adortl;DBXSybaseASEDriver;DBXDb2Driver;vclimg;DataSnapFireDAC;emsclientfiredac;FireDACPgDriver;FireDAC;FireDACDSDriver;inetdbxpress;xmlrtl;tethering;dmvcframeworkRT;bindcompvcl;dsnap;CloudService;DBXSybaseASADriver;DBXOracleDriver;FireDACDb2Driver;DBXInformixDriver;fmxobj;bindcompvclsmp;DataSnapNativeClient;DatasnapConnectorsFreePascal;soaprtl;soapserver;FireDACIBDriver;$(DCC_UsePackage) + Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace) + Debug + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments= + 1033 + + + vclwinx;DataSnapServer;fmx;emshosting;vclie;DbxCommonDriver;bindengine;IndyIPCommon;VCLRESTComponents;DBXMSSQLDriver;FireDACCommonODBC;emsclient;FireDACCommonDriver;appanalytics;IndyProtocols;vclx;IndyIPClient;dbxcds;vcledge;bindcompvclwinx;emsedge;bindcompfmx;DBXFirebirdDriver;inetdb;FireDACSqliteDriver;DbxClientDriver;FireDACASADriver;soapmidas;vclactnband;fmxFireDAC;dbexpress;FireDACInfxDriver;DBXMySQLDriver;VclSmp;inet;DataSnapCommon;vcltouch;fmxase;DBXOdbcDriver;dbrtl;FireDACDBXDriver;FireDACOracleDriver;fmxdae;FireDACMSAccDriver;CustomIPTransport;FireDACMSSQLDriver;DataSnapIndy10ServerTransport;DataSnapConnectors;vcldsnap;DBXInterBaseDriver;FireDACMongoDBDriver;IndySystem;FireDACTDataDriver;Skia.Package.VCL;vcldb;StyledComponents;vclFireDAC;bindcomp;FireDACCommon;DataSnapServerMidas;FireDACODBCDriver;emsserverresource;IndyCore;RESTBackendComponents;bindcompdbx;rtl;FireDACMySQLDriver;FireDACADSDriver;StyledAnimatedComponents;RESTComponents;DBXSqliteDriver;vcl;IndyIPServer;dsnapxml;dsnapcon;DataSnapClient;DataSnapProviderClient;adortl;DBXSybaseASEDriver;DBXDb2Driver;vclimg;DataSnapFireDAC;emsclientfiredac;FireDACPgDriver;FireDAC;FireDACDSDriver;inetdbxpress;xmlrtl;tethering;bindcompvcl;dsnap;CloudService;DBXSybaseASADriver;DBXOracleDriver;FireDACDb2Driver;DBXInformixDriver;fmxobj;bindcompvclsmp;DataSnapNativeClient;DatasnapConnectorsFreePascal;soaprtl;soapserver;FireDACIBDriver;$(DCC_UsePackage) + + + DEBUG;$(DCC_Define) + true + false + true + true + true + true + true + + + false + + + false + RELEASE;$(DCC_Define) + 0 + 0 + + + + MainSource + + + + + +
MyWebModule
+ dfm + TWebModule +
+ + Base + + + Cfg_1 + Base + + + Cfg_2 + Base + +
+ + Delphi.Personality.12 + Console + + + + sqids_sample.dpr + + + + + + true + + + + + true + + + + + true + + + + + sqids_sample.exe + true + + + + + 1 + + + 0 + + + + + classes + 64 + + + classes + 64 + + + + + res\xml + 1 + + + res\xml + 1 + + + + + library\lib\armeabi + 1 + + + library\lib\armeabi + 1 + + + + + library\lib\armeabi-v7a + 1 + + + + + library\lib\mips + 1 + + + library\lib\mips + 1 + + + + + library\lib\armeabi-v7a + 1 + + + library\lib\arm64-v8a + 1 + + + + + library\lib\armeabi-v7a + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\drawable-anydpi-v21 + 1 + + + res\drawable-anydpi-v21 + 1 + + + + + res\values + 1 + + + res\values + 1 + + + + + res\values-v21 + 1 + + + res\values-v21 + 1 + + + + + res\values-v31 + 1 + + + res\values-v31 + 1 + + + + + res\drawable-anydpi-v26 + 1 + + + res\drawable-anydpi-v26 + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\drawable-anydpi-v33 + 1 + + + res\drawable-anydpi-v33 + 1 + + + + + res\values + 1 + + + res\values + 1 + + + + + res\values-night-v21 + 1 + + + res\values-night-v21 + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\drawable-xxhdpi + 1 + + + res\drawable-xxhdpi + 1 + + + + + res\drawable-xxxhdpi + 1 + + + res\drawable-xxxhdpi + 1 + + + + + res\drawable-ldpi + 1 + + + res\drawable-ldpi + 1 + + + + + res\drawable-mdpi + 1 + + + res\drawable-mdpi + 1 + + + + + res\drawable-hdpi + 1 + + + res\drawable-hdpi + 1 + + + + + res\drawable-xhdpi + 1 + + + res\drawable-xhdpi + 1 + + + + + res\drawable-mdpi + 1 + + + res\drawable-mdpi + 1 + + + + + res\drawable-hdpi + 1 + + + res\drawable-hdpi + 1 + + + + + res\drawable-xhdpi + 1 + + + res\drawable-xhdpi + 1 + + + + + res\drawable-xxhdpi + 1 + + + res\drawable-xxhdpi + 1 + + + + + res\drawable-xxxhdpi + 1 + + + res\drawable-xxxhdpi + 1 + + + + + res\drawable-small + 1 + + + res\drawable-small + 1 + + + + + res\drawable-normal + 1 + + + res\drawable-normal + 1 + + + + + res\drawable-large + 1 + + + res\drawable-large + 1 + + + + + res\drawable-xlarge + 1 + + + res\drawable-xlarge + 1 + + + + + res\values + 1 + + + res\values + 1 + + + + + res\drawable-anydpi-v24 + 1 + + + res\drawable-anydpi-v24 + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\drawable-night-anydpi-v21 + 1 + + + res\drawable-night-anydpi-v21 + 1 + + + + + res\drawable-anydpi-v31 + 1 + + + res\drawable-anydpi-v31 + 1 + + + + + res\drawable-night-anydpi-v31 + 1 + + + res\drawable-night-anydpi-v31 + 1 + + + + + 1 + + + 1 + + + 0 + + + + + 1 + .framework + + + 1 + .framework + + + 1 + .framework + + + 0 + + + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + 0 + .dll;.bpl + + + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + 0 + .bpl + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 1 + + + 1 + + + + + + + + Contents\Resources + 1 + + + Contents\Resources + 1 + + + Contents\Resources + 1 + + + + + library\lib\armeabi-v7a + 1 + + + library\lib\arm64-v8a + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 0 + + + + + library\lib\armeabi-v7a + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + + + + + + 1 + + + 1 + + + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + + + + + + + + + + + + + False + False + False + True + False + + + 12 + + + + +
diff --git a/sources/MVCFramework.Commons.pas b/sources/MVCFramework.Commons.pas index 98332f0f..4dcf9e71 100644 --- a/sources/MVCFramework.Commons.pas +++ b/sources/MVCFramework.Commons.pas @@ -43,7 +43,7 @@ uses System.Generics.Collections, MVCFramework.DuckTyping, JsonDataObjects, - MVCFramework.DotEnv, MVCFramework.Container; + MVCFramework.DotEnv, MVCFramework.Container, sqids; {$I dmvcframeworkbuildconsts.inc} @@ -115,7 +115,7 @@ type TMVCConstants = record public const SESSION_TOKEN_NAME = 'dtsessionid'; - DEFAULT_CONTENT_CHARSET = 'UTF-8'; + DEFAULT_CONTENT_CHARSET = TMVCCharSet.UTF_8; DEFAULT_CONTENT_TYPE = TMVCMediaType.APPLICATION_JSON; CURRENT_USER_SESSION_KEY = '__DMVC_CURRENT_USER__'; LAST_AUTHORIZATION_HEADER_VALUE = '__DMVC_LAST_AUTHORIZATION_HEADER_VALUE_'; @@ -774,9 +774,23 @@ type VPassword: string; var VHandled: Boolean); end; + TMVCSqids = class sealed + private + class var fInstance: TSqids; + public + class var SQIDS_ALPHABET: String; + class var SQIDS_MIN_LENGTH: Integer; + class destructor Destroy; + class function GetInstance: TSqids; + { sqids } + class function SqidToInt(const Sqid: String): UInt64; + class function IntToSqid(const Value: UInt64): String; + end; + function dotEnv: IMVCDotEnv; overload; procedure dotEnvConfigure(const dotEnvDelegate: TFunc); + implementation uses @@ -786,7 +800,8 @@ uses MVCFramework.Serializer.JsonDataObjects, MVCFramework.Serializer.Commons, MVCFramework.Utils, - System.RegularExpressions, MVCFramework.Logger; + System.RegularExpressions, + MVCFramework.Logger; var GlobalAppName, GlobalAppPath, GlobalAppExe: string; @@ -795,6 +810,38 @@ var GdotEnv: IMVCDotEnv = nil; GdotEnvDelegate: TFunc = nil; +class destructor TMVCSqids.Destroy; +begin + FreeAndNil(fInstance); +end; + +class function TMVCSqids.GetInstance: TSqids; +begin + if fInstance = nil then + begin + TMonitor.Enter(gLock); + try + if fInstance = nil then + begin + fInstance := TSqids.Create(SQIDS_ALPHABET, SQIDS_MIN_LENGTH); + end; + finally + TMonitor.Exit(gLock); + end; + end; + Result := fInstance; +end; + +class function TMVCSqids.IntToSqid(const Value: UInt64): String; +begin + Result := GetInstance.EncodeSingle(Value); +end; + +class function TMVCSqids.SqidToInt(const Sqid: String): UInt64; +begin + Result := GetInstance.DecodeSingle(Sqid); +end; + function URLEncode(const Value: string): string; overload; begin {$IF defined(BERLINORBETTER)} @@ -1839,6 +1886,6 @@ GlobalAppPath := IncludeTrailingPathDelimiter(ExtractFilePath(GetModuleName(HIns finalization -FreeAndNil(gLock); +FreeAndNil(GLock); end. diff --git a/sources/MVCFramework.Router.pas b/sources/MVCFramework.Router.pas index add33c8a..1ca23118 100644 --- a/sources/MVCFramework.Router.pas +++ b/sources/MVCFramework.Router.pas @@ -43,13 +43,13 @@ type TMVCActionParamCacheItem = class private FValue: string; - FParams: TList; + FParams: TList>; FRegEx: TRegEx; public - constructor Create(aValue: string; aParams: TList); virtual; + constructor Create(aValue: string; aParams: TList>); virtual; destructor Destroy; override; function Value: string; - function Params: TList; // this should be read-only... + function Params: TList>; // this should be read-only... function Match(const Value: String): TMatch; inline; end; @@ -83,7 +83,7 @@ type const AMVCPath: string; const APath: string; var aParams: TMVCRequestParamsTable): Boolean; - function GetParametersNames(const V: string): TList; + function GetParametersNames(const V: string): TList>; protected procedure FillControllerMappedPaths( const aControllerName: string; @@ -347,23 +347,32 @@ function TMVCRouter.IsCompatiblePath( const APath: string; var aParams: TMVCRequestParamsTable): Boolean; - function ToPattern(const V: string; const Names: TList): string; + function ToPattern(const V: string; const Names: TList>): string; var - S: string; + S: TPair; begin Result := V; - for S in Names do - Result := StringReplace(Result, '($' + S + ')', '([' + TMVCConstants.URL_MAPPED_PARAMS_ALLOWED_CHARS + ']*)', - [rfReplaceAll]); + if Names.Count > 0 then + begin + for S in Names do + begin + Result := StringReplace( + Result, + '($' + S.Key + S.Value + ')', '([' + TMVCConstants.URL_MAPPED_PARAMS_ALLOWED_CHARS + ']*)', + [rfReplaceAll]); + end; + end; end; var -// lRegEx: TRegEx; lMatch: TMatch; lPattern: string; I: Integer; - lNames: TList; + lNames: TList>; lCacheItem: TMVCActionParamCacheItem; + P: TPair; + lConv: string; + lParValue: String; begin if (APath = AMVCPath) or ((APath = '/') and (AMVCPath = '')) then begin @@ -375,46 +384,85 @@ begin lNames := GetParametersNames(AMVCPath); lPattern := ToPattern(AMVCPath, lNames); lCacheItem := TMVCActionParamCacheItem.Create('^' + lPattern + '$', lNames); - FActionParamsCache.Add(AMVCPath, lCacheItem); + FActionParamsCache.Add(AMVCPath, lCacheItem); {do not commit this!} end; -// lRegEx := TRegEx.Create(lCacheItem.Value, [roIgnoreCase, roCompiled, roSingleLine]); -// lMatch := lRegEx.Match(APath); - lMatch := lCacheItem.Match(APath); Result := lMatch.Success; if Result then begin for I := 1 to Pred(lMatch.Groups.Count) do begin - aParams.Add(lCacheItem.Params[I - 1], TIdURI.URLDecode(lMatch.Groups[I].Value)); + P := lCacheItem.Params[I - 1]; + + { + P.Key = Parameter name + P.Value = Converter applied to the value before to be injected (eg. :sqid) + } + + lParValue := TIdURI.URLDecode(lMatch.Groups[I].Value); + if P.Value.IsEmpty then + begin + {no converter} + aParams.Add(P.Key, lParValue); + end + else + begin + lConv := P.Value; + if SameText(lConv, ':sqid') then + begin + {sqids converter (so far the only one)} + aParams.Add(P.Key, TMVCSqids.SqidToInt(lParValue).ToString); + end + else + begin + raise EMVCException.CreateFmt('Unknown converter: %s', [lConv]); + end; + end; end; end; end; -function TMVCRouter.GetParametersNames(const V: string): TList; +function TMVCRouter.GetParametersNames(const V: string): TList>; var S: string; Matches: TMatchCollection; M: TMatch; I: Integer; - lList: TList; + lList: TList>; + lNameFound: Boolean; + lConverter: string; + lName: string; begin - lList := TList.Create; + lList := TList>.Create; try - S := '\(\$([A-Za-z0-9\_]+)\)'; + S := '\(\$([A-Za-z0-9\_]+)(\:[a-z]+)?\)'; Matches := TRegEx.Matches(V, S, [roIgnoreCase, roCompiled, roSingleLine]); for M in Matches do begin + lNameFound := False; + lConverter := ''; for I := 0 to M.Groups.Count - 1 do begin S := M.Groups[I].Value; - if (Length(S) > 0) and (S.Chars[0] <> '(') then + if Length(S) > 0 then begin - lList.Add(S); - Break; + if (not lNameFound) and (S.Chars[0] <> '(') and (S.Chars[0] <> ':') then + begin + lName := S; + lNameFound := True; + Continue; + end; + if lNameFound and (S.Chars[0] = ':') then + begin + lConverter := S; + end; end; end; + if lNameFound then + begin + lList.Add(TPair.Create(lName,lConverter)); + end; end; Result := lList; except @@ -533,12 +581,12 @@ end; { TMVCActionParamCacheItem } constructor TMVCActionParamCacheItem.Create(aValue: string; - aParams: TList); + aParams: TList>); begin inherited Create; - FValue := aValue; - FParams := aParams; - FRegEx := TRegEx.Create(FValue, [roIgnoreCase, roCompiled, roSingleLine]); + fValue := aValue; + fParams := aParams; + fRegEx := TRegEx.Create(FValue, [roIgnoreCase, roCompiled, roSingleLine]); end; destructor TMVCActionParamCacheItem.Destroy; @@ -552,7 +600,7 @@ begin Result := fRegEx.Match(Value); end; -function TMVCActionParamCacheItem.Params: TList; +function TMVCActionParamCacheItem.Params: TList>; begin Result := FParams; end; diff --git a/sources/MVCFramework.pas b/sources/MVCFramework.pas index e5ea8f4e..45f47d64 100644 --- a/sources/MVCFramework.pas +++ b/sources/MVCFramework.pas @@ -330,6 +330,10 @@ type end; + MVCFromSqidAttribute = class(MVCInjectableParamAttribute) + + end; + MVCFromCookieAttribute = class(MVCInjectableParamAttribute) end; @@ -4070,7 +4074,7 @@ end; function TMVCRenderer.GetContentType: string; begin Result := GetContext.Response.ContentType; - if Result.IsEmpty then + if Result.IsEmpty or FContentCharset.IsEmpty then begin Result := FContext.FConfig[MVCFramework.Commons.TMVCConfigKey.DefaultContentType]; GetContext.Response.ContentType := Result; @@ -4776,14 +4780,14 @@ procedure TMVCRenderer.RenderSSE(const EventID, EventData: string; EventName: st const Retry: Integer); begin // setting up the correct SSE headers - SetContentType('text/event-stream'); + SetContentType(BuildContentType(TMVCMediaType.TEXT_EVENTSTREAM, TMVCCharSet.UTF_8)); GetContext.Response.SetCustomHeader('Cache-Control', 'no-cache'); - GetContext.Response.StatusCode := http_status.OK; + GetContext.Response.StatusCode := HTTP_STATUS.OK; // render the response using SSE compliant data format // current event id (the client will resend this number at the next request) - ResponseStream.Append(Format('id: %s'#13, [EventID])); + ResponseStream.Append(Format('id:%s'#13, [EventID])); // The browser attempts to reconnect to the source roughly 3 seconds after // each connection is closed. You can change that timeout by including a line @@ -4792,16 +4796,16 @@ begin if Retry > -1 then begin - ResponseStream.Append(Format('retry: %d'#13, [Retry])); + ResponseStream.Append(Format('retry:%d'#13, [Retry])); end; if not EventName.IsEmpty then begin - ResponseStream.Append(Format('event: %s'#13, [EventName])); + ResponseStream.Append(Format('event:%s'#13, [EventName])); end; // actual message - ResponseStream.Append('data: ' + EventData.Replace(sLineBreak, '', [rfReplaceAll]) + #13#13); + ResponseStream.Append('data:' + EventData.Replace(sLineBreak, '', [rfReplaceAll]) + #13#13); // render all the stuff RenderResponseStream; diff --git a/sources/blocklist.inc b/sources/blocklist.inc new file mode 100644 index 00000000..900c146c --- /dev/null +++ b/sources/blocklist.inc @@ -0,0 +1,564 @@ +const + DEFAULT_BLOCKLIST: TArray = [ + '0rgasm', + '1d10t', + '1d1ot', + '1di0t', + '1diot', + '1eccacu10', + '1eccacu1o', + '1eccacul0', + '1eccaculo', + '1mbec11e', + '1mbec1le', + '1mbeci1e', + '1mbecile', + 'a11upat0', + 'a11upato', + 'a1lupat0', + 'a1lupato', + 'aand', + 'ah01e', + 'ah0le', + 'aho1e', + 'ahole', + 'al1upat0', + 'al1upato', + 'allupat0', + 'allupato', + 'ana1', + 'ana1e', + 'anal', + 'anale', + 'anus', + 'arrapat0', + 'arrapato', + 'arsch', + 'arse', + 'ass', + 'b00b', + 'b00be', + 'b01ata', + 'b0ceta', + 'b0iata', + 'b0ob', + 'b0obe', + 'b0sta', + 'b1tch', + 'b1te', + 'b1tte', + 'ba1atkar', + 'balatkar', + 'bastard0', + 'bastardo', + 'batt0na', + 'battona', + 'bitch', + 'bite', + 'bitte', + 'bo0b', + 'bo0be', + 'bo1ata', + 'boceta', + 'boiata', + 'boob', + 'boobe', + 'bosta', + 'bran1age', + 'bran1er', + 'bran1ette', + 'bran1eur', + 'bran1euse', + 'branlage', + 'branler', + 'branlette', + 'branleur', + 'branleuse', + 'c0ck', + 'c0g110ne', + 'c0g11one', + 'c0g1i0ne', + 'c0g1ione', + 'c0gl10ne', + 'c0gl1one', + 'c0gli0ne', + 'c0glione', + 'c0na', + 'c0nnard', + 'c0nnasse', + 'c0nne', + 'c0u111es', + 'c0u11les', + 'c0u1l1es', + 'c0u1lles', + 'c0ui11es', + 'c0ui1les', + 'c0uil1es', + 'c0uilles', + 'c11t', + 'c11t0', + 'c11to', + 'c1it', + 'c1it0', + 'c1ito', + 'cabr0n', + 'cabra0', + 'cabrao', + 'cabron', + 'caca', + 'cacca', + 'cacete', + 'cagante', + 'cagar', + 'cagare', + 'cagna', + 'cara1h0', + 'cara1ho', + 'caracu10', + 'caracu1o', + 'caracul0', + 'caraculo', + 'caralh0', + 'caralho', + 'cazz0', + 'cazz1mma', + 'cazzata', + 'cazzimma', + 'cazzo', + 'ch00t1a', + 'ch00t1ya', + 'ch00tia', + 'ch00tiya', + 'ch0d', + 'ch0ot1a', + 'ch0ot1ya', + 'ch0otia', + 'ch0otiya', + 'ch1asse', + 'ch1avata', + 'ch1er', + 'ch1ng0', + 'ch1ngadaz0s', + 'ch1ngadazos', + 'ch1ngader1ta', + 'ch1ngaderita', + 'ch1ngar', + 'ch1ngo', + 'ch1ngues', + 'ch1nk', + 'chatte', + 'chiasse', + 'chiavata', + 'chier', + 'ching0', + 'chingadaz0s', + 'chingadazos', + 'chingader1ta', + 'chingaderita', + 'chingar', + 'chingo', + 'chingues', + 'chink', + 'cho0t1a', + 'cho0t1ya', + 'cho0tia', + 'cho0tiya', + 'chod', + 'choot1a', + 'choot1ya', + 'chootia', + 'chootiya', + 'cl1t', + 'cl1t0', + 'cl1to', + 'clit', + 'clit0', + 'clito', + 'cock', + 'cog110ne', + 'cog11one', + 'cog1i0ne', + 'cog1ione', + 'cogl10ne', + 'cogl1one', + 'cogli0ne', + 'coglione', + 'cona', + 'connard', + 'connasse', + 'conne', + 'cou111es', + 'cou11les', + 'cou1l1es', + 'cou1lles', + 'coui11es', + 'coui1les', + 'couil1es', + 'couilles', + 'cracker', + 'crap', + 'cu10', + 'cu1att0ne', + 'cu1attone', + 'cu1er0', + 'cu1ero', + 'cu1o', + 'cul0', + 'culatt0ne', + 'culattone', + 'culer0', + 'culero', + 'culo', + 'cum', + 'cunt', + 'd11d0', + 'd11do', + 'd1ck', + 'd1ld0', + 'd1ldo', + 'damn', + 'de1ch', + 'deich', + 'depp', + 'di1d0', + 'di1do', + 'dick', + 'dild0', + 'dildo', + 'dyke', + 'encu1e', + 'encule', + 'enema', + 'enf01re', + 'enf0ire', + 'enfo1re', + 'enfoire', + 'estup1d0', + 'estup1do', + 'estupid0', + 'estupido', + 'etr0n', + 'etron', + 'f0da', + 'f0der', + 'f0ttere', + 'f0tters1', + 'f0ttersi', + 'f0tze', + 'f0utre', + 'f1ca', + 'f1cker', + 'f1ga', + 'fag', + 'fica', + 'ficker', + 'figa', + 'foda', + 'foder', + 'fottere', + 'fotters1', + 'fottersi', + 'fotze', + 'foutre', + 'fr0c10', + 'fr0c1o', + 'fr0ci0', + 'fr0cio', + 'fr0sc10', + 'fr0sc1o', + 'fr0sci0', + 'fr0scio', + 'froc10', + 'froc1o', + 'froci0', + 'frocio', + 'frosc10', + 'frosc1o', + 'frosci0', + 'froscio', + 'fuck', + 'g00', + 'g0o', + 'g0u1ne', + 'g0uine', + 'gandu', + 'go0', + 'goo', + 'gou1ne', + 'gouine', + 'gr0gnasse', + 'grognasse', + 'haram1', + 'harami', + 'haramzade', + 'hund1n', + 'hundin', + 'id10t', + 'id1ot', + 'idi0t', + 'idiot', + 'imbec11e', + 'imbec1le', + 'imbeci1e', + 'imbecile', + 'j1zz', + 'jerk', + 'jizz', + 'k1ke', + 'kam1ne', + 'kamine', + 'kike', + 'leccacu10', + 'leccacu1o', + 'leccacul0', + 'leccaculo', + 'm1erda', + 'm1gn0tta', + 'm1gnotta', + 'm1nch1a', + 'm1nchia', + 'm1st', + 'mam0n', + 'mamahuev0', + 'mamahuevo', + 'mamon', + 'masturbat10n', + 'masturbat1on', + 'masturbate', + 'masturbati0n', + 'masturbation', + 'merd0s0', + 'merd0so', + 'merda', + 'merde', + 'merdos0', + 'merdoso', + 'mierda', + 'mign0tta', + 'mignotta', + 'minch1a', + 'minchia', + 'mist', + 'musch1', + 'muschi', + 'n1gger', + 'neger', + 'negr0', + 'negre', + 'negro', + 'nerch1a', + 'nerchia', + 'nigger', + 'orgasm', + 'p00p', + 'p011a', + 'p01la', + 'p0l1a', + 'p0lla', + 'p0mp1n0', + 'p0mp1no', + 'p0mpin0', + 'p0mpino', + 'p0op', + 'p0rca', + 'p0rn', + 'p0rra', + 'p0uff1asse', + 'p0uffiasse', + 'p1p1', + 'p1pi', + 'p1r1a', + 'p1rla', + 'p1sc10', + 'p1sc1o', + 'p1sci0', + 'p1scio', + 'p1sser', + 'pa11e', + 'pa1le', + 'pal1e', + 'palle', + 'pane1e1r0', + 'pane1e1ro', + 'pane1eir0', + 'pane1eiro', + 'panele1r0', + 'panele1ro', + 'paneleir0', + 'paneleiro', + 'patakha', + 'pec0r1na', + 'pec0rina', + 'pecor1na', + 'pecorina', + 'pen1s', + 'pendej0', + 'pendejo', + 'penis', + 'pip1', + 'pipi', + 'pir1a', + 'pirla', + 'pisc10', + 'pisc1o', + 'pisci0', + 'piscio', + 'pisser', + 'po0p', + 'po11a', + 'po1la', + 'pol1a', + 'polla', + 'pomp1n0', + 'pomp1no', + 'pompin0', + 'pompino', + 'poop', + 'porca', + 'porn', + 'porra', + 'pouff1asse', + 'pouffiasse', + 'pr1ck', + 'prick', + 'pussy', + 'put1za', + 'puta', + 'puta1n', + 'putain', + 'pute', + 'putiza', + 'puttana', + 'queca', + 'r0mp1ba11e', + 'r0mp1ba1le', + 'r0mp1bal1e', + 'r0mp1balle', + 'r0mpiba11e', + 'r0mpiba1le', + 'r0mpibal1e', + 'r0mpiballe', + 'rand1', + 'randi', + 'rape', + 'recch10ne', + 'recch1one', + 'recchi0ne', + 'recchione', + 'retard', + 'romp1ba11e', + 'romp1ba1le', + 'romp1bal1e', + 'romp1balle', + 'rompiba11e', + 'rompiba1le', + 'rompibal1e', + 'rompiballe', + 'ruff1an0', + 'ruff1ano', + 'ruffian0', + 'ruffiano', + 's1ut', + 'sa10pe', + 'sa1aud', + 'sa1ope', + 'sacanagem', + 'sal0pe', + 'salaud', + 'salope', + 'saugnapf', + 'sb0rr0ne', + 'sb0rra', + 'sb0rrone', + 'sbattere', + 'sbatters1', + 'sbattersi', + 'sborr0ne', + 'sborra', + 'sborrone', + 'sc0pare', + 'sc0pata', + 'sch1ampe', + 'sche1se', + 'sche1sse', + 'scheise', + 'scheisse', + 'schlampe', + 'schwachs1nn1g', + 'schwachs1nnig', + 'schwachsinn1g', + 'schwachsinnig', + 'schwanz', + 'scopare', + 'scopata', + 'sexy', + 'sh1t', + 'shit', + 'slut', + 'sp0mp1nare', + 'sp0mpinare', + 'spomp1nare', + 'spompinare', + 'str0nz0', + 'str0nza', + 'str0nzo', + 'stronz0', + 'stronza', + 'stronzo', + 'stup1d', + 'stupid', + 'succh1am1', + 'succh1ami', + 'succhiam1', + 'succhiami', + 'sucker', + 't0pa', + 'tapette', + 'test1c1e', + 'test1cle', + 'testic1e', + 'testicle', + 'tette', + 'topa', + 'tr01a', + 'tr0ia', + 'tr0mbare', + 'tr1ng1er', + 'tr1ngler', + 'tring1er', + 'tringler', + 'tro1a', + 'troia', + 'trombare', + 'turd', + 'twat', + 'vaffancu10', + 'vaffancu1o', + 'vaffancul0', + 'vaffanculo', + 'vag1na', + 'vagina', + 'verdammt', + 'verga', + 'w1chsen', + 'wank', + 'wichsen', + 'x0ch0ta', + 'x0chota', + 'xana', + 'xoch0ta', + 'xochota', + 'z0cc01a', + 'z0cc0la', + 'z0cco1a', + 'z0ccola', + 'z1z1', + 'z1zi', + 'ziz1', + 'zizi', + 'zocc01a', + 'zocc0la', + 'zocco1a', + 'zoccola' + ]; + diff --git a/sources/sqids.pas b/sources/sqids.pas new file mode 100644 index 00000000..71d4edcb --- /dev/null +++ b/sources/sqids.pas @@ -0,0 +1,396 @@ +unit sqids; + +{$IFDEF FPC} +{$mode delphi} +{$ENDIF} + +interface + +uses + {$IFDEF FPC} + SysUtils, StrUtils + {$ELSE} + System.SysUtils, System.StrUtils + {$ENDIF} + ; + +const + DEFAULT_ALPHABET = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + DEFAULT_MIN_LENGTH = 0; + MIN_ALPHABET_LENGTH = 3; + MAX_ALPHABET_LENGTH = High(Byte); + {$I blocklist.inc} + +type + TNumber = UInt64; + TNumbers = TArray; + + { TSqids } + + TSqids = class + private + FAlphabet: string; + FMinLength: Byte; + FBlockList: TArray; + function Shuffle(AAlphabet: string): string; + function ToId(ANum: TNumber; AAlphabet: string): string; + function ToNumber(AId: string; AAlphabet: string): TNumber; + function IsBlocked(AId: string): Boolean; + function EncodeNumbers(ANumbers: TNumbers; AIncrement: Word = 0): string; + public + constructor Create(AAlphabet: string; AMinLength: Byte; + ABlockList: TArray); overload; + constructor Create(AAlphabet: string = DEFAULT_ALPHABET; + AMinLength: Byte = DEFAULT_MIN_LENGTH); overload; + constructor Create(AMinLength: Byte); overload; + constructor Create(ABlockList: TArray); overload; + + function Encode(ANumbers: TNumbers): string; + function EncodeSingle(ANumber: TNumber): string; + function Decode(AId: string): TNumbers; + function DecodeSingle(AId: string): TNumber; + end; + + ESqidsException = class(Exception); + +implementation + +{ TSqids } + +constructor TSqids.Create(AAlphabet: string; AMinLength: Byte; ABlockList: TArray); +var + I: Integer; + LFilteredBlockList: TArray; + LAlphabetLower: string; + C: Char; + LCharsInAlphabet: Boolean; +begin + inherited Create; + + if AAlphabet = '' then + AAlphabet := DEFAULT_ALPHABET; + + for C in AAlphabet do + if Ord(C) > 127 then + raise ESqidsException.Create('Alphabet cannot contain multibyte characters'); + + if Length(AAlphabet) < MIN_ALPHABET_LENGTH then + raise ESqidsException.CreateFmt('Alphabet length must be at least %d', [MIN_ALPHABET_LENGTH]); + + if Length(AAlphabet) > MAX_ALPHABET_LENGTH then + raise ESqidsException.CreateFmt('Alphabet length must not be longer than %d', [MAX_ALPHABET_LENGTH]); + + if Pos(' ', AAlphabet) > 0 then + raise ESqidsException.Create('Alphabet must not contain spaces'); + + for I := 1 to Length(AAlphabet) do + if Pos(AAlphabet[I], AAlphabet, I+1) > 0 then + raise ESqidsException.Create('Alphabet must contain unique characters'); + + // clean up blocklist: + // 1. all blocklist words should be lowercase + // 2. no words less than 3 chars + // 3. if some words contain chars that are not in the alphabet, remove those + LFilteredBlockList := Copy(ABlockList, 0); + LAlphabetLower := Lowercase(AAlphabet); + I := 0; + while I < Length(LFilteredBlockList) do + if Length(LFilteredBlockList[I]) < 3 then + Delete(LFilteredBlockList, I, 1) + else + begin + LFilteredBlockList[I] := Lowercase(LFilteredBlockList[I]); + + LCharsInAlphabet := True; + for C in LFilteredBlockList[I] do + LCharsInAlphabet := LCharsInAlphabet and LAlphabetLower.Contains(C); + + if not LCharsInAlphabet then + Delete(LFilteredBlockList, I, 1) + else + Inc(I); + end; + + FAlphabet := Shuffle(AAlphabet); + FMinLength := AMinLength; + FBlockList := LFilteredBlockList; +end; + +constructor TSqids.Create(AAlphabet: string; AMinLength: Byte); +begin + Create(AAlphabet, AMinLength, DEFAULT_BLOCKLIST); +end; + +constructor TSqids.Create(AMinLength: Byte); +begin + Create(DEFAULT_ALPHABET, AMinLength, DEFAULT_BLOCKLIST); +end; + +constructor TSqids.Create(ABlockList: TArray); +begin + Create(DEFAULT_ALPHABET, DEFAULT_MIN_LENGTH, ABlockList); +end; + +// consistent shuffle (always produces the same result given the input) +function TSqids.Shuffle(AAlphabet: string): string; +var + I: Integer; + J: Integer; + L: Integer; + R: Integer; + C: Char; +begin + Result := AAlphabet; + + L := Length(Result); + I := 0; + J := L - 1; + while J > 0 do + begin + // In Pascal, string index is 1-based + // So when accessing string chars, it is needed to add 1, e.g. [I+1] + R := (I * J + Ord(Result[I+1]) + Ord(Result[J+1])) mod L; + + // swap characters at position I+1 and R+1 + C := Result[I+1]; + Result[I+1] := Result[R+1]; + Result[R+1] := C; + + Inc(I); + Dec(J); + end; +end; + +function TSqids.ToId(ANum: TNumber; AAlphabet: string): string; +var + L: Byte; + LNumResult: TNumber; +begin + Result := ''; + L := Length(AAlphabet); + LNumResult := ANum; + + repeat + Result := AAlphabet[(LNumResult mod L) + 1] + Result; + LNumResult := LNumResult div L; + until LNumResult = 0; +end; + +function TSqids.ToNumber(AId: string; AAlphabet: string): TNumber; +var + C: Char; + L: Byte; +begin + Result := 0; + L := Length(AAlphabet); + + for C in AId do + Result := Result * L + TNumber(Pos(C, AAlphabet)) - 1; +end; + +function TSqids.IsBlocked(AId: string): Boolean; + + function ContainsDigits(S: string): Boolean; + var + C: Char; + begin + for C in S do + if CharInSet(C, ['0'..'9']) then + Exit(True); + + Result := False; + end; + +var + LWord: string; +begin + AId := AId.ToLower; + + for LWord in FBlockList do + // no point in checking words that are longer than the ID + if Length(LWord) <= Length(AId) then + if (Length(AId) <= 3) or (Length(LWord) <= 3) then + begin + // short words have to match completely; otherwise, too many matches + if AId = LWord then + Exit(True); + end + else if ContainsDigits(LWord) then + begin + // if blocklist words contain numbers (leetspeak), + // they will only trigger a match if they're at + // the beginning or the end of the ID. + if AId.StartsWith(LWord) or AId.EndsWith(LWord) then + Exit(True); + end + else if AId.Contains(LWord) then + Exit(True); + + Result := False; +end; + +function TSqids.Encode(ANumbers: TNumbers): string; +begin + if Length(ANumbers) = 0 then + Exit(''); + + // no range checking implemented; compiler enforces numbers are within + // range of TNumber type + + Result := EncodeNumbers(ANumbers); +end; + +function TSqids.EncodeSingle(ANumber: TNumber): string; +begin + Result := Encode([ANumber]); +end; + +function TSqids.EncodeNumbers(ANumbers: TNumbers; AIncrement: Word = 0): string; +var + LOffset: TNumber; + I: TNumber; + L: Byte; + LAlphabet: string; + LAlphabetWithoutSeparator: string; + LPrefix: Char; + LNum: TNumber; +begin + // if increment is greater than alphabet length, we've reached max attempts + if AIncrement > Length(FAlphabet) then + raise ESqidsException.Create('Reached max attempts to re-generate the ID'); + + // get a semi-random LOffset from input numbers + LOffset := Length(ANumbers); + L := Length(FAlphabet); + for I := 0 to Length(ANumbers) - 1 do + LOffset := LOffset + Ord(FAlphabet[(ANumbers[I] mod L) + 1]) + I; + LOffset := LOffset mod L; + + // if there is a non-zero `increment`, it's an internal attempt to re-generate the ID + LOffset := (LOffset + AIncrement) mod L; + + // re-arrange alphabet so that second-half goes in front of the first-half + LAlphabet := Copy(FAlphabet, LOffset + 1) + Copy(FAlphabet, 1, LOffset); + + // `prefix` is the first character in the generated ID, used for randomization + LPrefix := LAlphabet[1]; + + // reverse alphabet (otherwise for [0, x] `LOffset` and `separator` will be the same char) + LAlphabet := ReverseString(LAlphabet); + + // final ID will always have the `prefix` character at the beginning + Result := LPrefix; + + // encode input array + for I := 0 to Length(ANumbers) - 1 do + begin + LNum := ANumbers[I]; + + // the first character of the alphabet is going to be reserved for the `separator` + LAlphabetWithoutSeparator := Copy(LAlphabet, 2); + Result := Result + ToId(LNum, LAlphabetWithoutSeparator); + + // if not the last number + if I < TNumber(Length(ANumbers)) - 1 then + begin + // `separator` character is used to isolate numbers within the ID + Result := Result + LAlphabet[1]; + + // shuffle on every iteration + LAlphabet := Shuffle(LAlphabet); + end; + end; + + // handle `minLength` requirement, if the ID is too short + if FMinLength > Length(Result) then + begin + // append a separator + Result := Result + LAlphabet[1]; + + // keep appending `separator` + however much alphabet is needed + // for decoding: two separators next to each other is what tells us the rest are junk characters + while FMinLength - Length(Result) > 0 do + begin + LAlphabet := Shuffle(LAlphabet); + Result := Result + Copy(LAlphabet, 1, FMinLength - Length(Result)); + end; + end; + + // if ID has a blocked word anywhere, restart with a +1 increment + if IsBlocked(Result) then + Result := EncodeNumbers(ANumbers, AIncrement + 1); +end; + +function TSqids.Decode(AId: string): TNumbers; +var + C: Char; + LPrefix: Char; + LSeparator: Char; + LOffset: TNumber; + LAlphabet: string; + LAlphabetWithoutSeparator: string; + LChunks: TArray; +begin + Result := []; + if AId = '' then Exit; + + // if a character is not in the alphabet, return an empty array + for C in AId do + if not FAlphabet.Contains(C) then + Exit; + + // first character is always the `prefix` + LPrefix := AId[1]; + + // `offset` is the semi-random position that was generated during encoding + LOffset := Pos(LPrefix, FAlphabet) - 1; + + // re-arrange alphabet back into its original form + LAlphabet := Copy(FAlphabet, LOffset + 1) + Copy(FAlphabet, 1, LOffset); + + // reverse alphabet + LAlphabet := ReverseString(LAlphabet); + + // now it's safe to remove the prefix character from ID, it's not needed anymore + AId := Copy(AId, 2); + + // decode + while not AId.IsEmpty do + begin + LSeparator := LAlphabet[1]; + + // we need the first part to the left of the separator to decode the number + LChunks := AId.Split([LSeparator]); + if Length(LChunks) > 0 then + begin + // if chunk is empty, we are done (the rest are junk characters) + if LChunks[0] = '' then + Exit; + + // decode the number without using the `separator` character + LAlphabetWithoutSeparator := Copy(LAlphabet, 2); + Result := Result + [ToNumber(LChunks[0], LAlphabetWithoutSeparator)]; + + // if this ID has multiple numbers, shuffle the alphabet because that's what encoding function did + if Length(LChunks) > 1 then + LAlphabet := Shuffle(LAlphabet); + end; + + // `id` is now going to be everything to the right of the `separator` + Delete(LChunks, 0, 1); + AId := string.Join(LSeparator, LChunks); + end; +end; + +function TSqids.DecodeSingle(AId: string): TNumber; +var + LNumbers: TNumbers; +begin + LNumbers := Decode(AId); + if Length(LNumbers) = 1 then + Result := LNumbers[0] + else + raise ESqidsException.CreateFmt('%d numbers in Id, expected: 1', [Length(LNumbers)]); +end; + +end. diff --git a/unittests/general/Several/DMVCFrameworkTests.dpr b/unittests/general/Several/DMVCFrameworkTests.dpr index 3c848141..7e6eb606 100644 --- a/unittests/general/Several/DMVCFrameworkTests.dpr +++ b/unittests/general/Several/DMVCFrameworkTests.dpr @@ -148,6 +148,9 @@ end; begin ReportMemoryLeaksOnShutdown := True; UseConsoleLogger := False; + TMVCSqids.SQIDS_ALPHABET := 'axDlw8dRnsPCrbZIAEMFG4TQ6gc3iWtOy9v5NBz0LfSmuKV71JHkUhYpej2Xqo'; + TMVCSqids.SQIDS_MIN_LENGTH := 6; + {$IF Defined(CONSOLE_TESTRUNNER)} MainConsole(); {$ELSE} diff --git a/unittests/general/Several/DMVCFrameworkTests.dproj b/unittests/general/Several/DMVCFrameworkTests.dproj index 1f15cf3e..f78d213d 100644 --- a/unittests/general/Several/DMVCFrameworkTests.dproj +++ b/unittests/general/Several/DMVCFrameworkTests.dproj @@ -9,6 +9,7 @@ 3 Console DMVCFrameworkTests.dpr + DMVCFrameworkTests
true @@ -991,6 +992,9 @@ 1 + + 1 + @@ -1253,6 +1257,7 @@ + True diff --git a/unittests/general/Several/FrameworkTestsU.pas b/unittests/general/Several/FrameworkTestsU.pas index 6b20f6e7..84f2b218 100644 --- a/unittests/general/Several/FrameworkTestsU.pas +++ b/unittests/general/Several/FrameworkTestsU.pas @@ -85,9 +85,6 @@ type procedure TestPathPrefix; [Test] procedure TestReservedIPs; - // procedure TestRoutingSpeed; - - // objects mappers end; [TestFixture] @@ -288,6 +285,14 @@ type procedure TestInLineComments; end; + [TestFixture] + TTestSqids = class(TObject) + public + [Test] + procedure TestSingle; + end; + + implementation {$WARN SYMBOL_DEPRECATED OFF} @@ -2397,6 +2402,14 @@ end; +{ TTestSqids } + +procedure TTestSqids.TestSingle; +begin + Assert.AreEqual('Im1JUf',TMVCSqids.IntToSqid(1)); {https://sqids.org/playground} + Assert.AreEqual(1, TMVCSqids.SqidToInt(TMVCSqids.IntToSqid(1))); +end; + initialization TDUnitX.RegisterTestFixture(TTestRouting); @@ -2408,6 +2421,7 @@ TDUnitX.RegisterTestFixture(TTestCryptUtils); TDUnitX.RegisterTestFixture(TTestLRUCache); TDUnitX.RegisterTestFixture(TTestDotEnv); TDUnitX.RegisterTestFixture(TTestDotEnvParser); +TDUnitX.RegisterTestFixture(TTestSqids); finalization diff --git a/unittests/general/Several/LiveServerTestU.pas b/unittests/general/Several/LiveServerTestU.pas index e1e866b0..5f6cc3da 100644 --- a/unittests/general/Several/LiveServerTestU.pas +++ b/unittests/general/Several/LiveServerTestU.pas @@ -266,6 +266,19 @@ type [Test] procedure TestSerializeAndDeserializeNullables_Passing_Integers_InsteadOf_Floats; + //test sqids + [Test] + [TestCase('1', '1,Im1JUf')] + [TestCase('2','1234567890,LhXiwKz')] + [TestCase('3','9007199254740991,PTP7uQmcmk')] + procedure TestSqidSingle(IntValue: UInt64; Sqid: String); + + [Test] + procedure TestWrongSqid; + + [Test] + procedure TestInvalidConverter; + // test responses objects [Test] procedure TestResponseCreated; @@ -1776,6 +1789,14 @@ begin Assert.areEqual('', res.Content); end; +procedure TServerTest.TestInvalidConverter; +var + lRes: IMVCRESTResponse; +begin + lRes := RESTClient.Get('/wrongconverter/1'); + Assert.areEqual(500, lRes.StatusCode); +end; + procedure TServerTest.TestIssue406; var r: IMVCRESTResponse; @@ -2834,6 +2855,16 @@ begin end; end; +procedure TServerTest.TestSqidSingle(IntValue: UInt64; Sqid: String); +var + lRes: IMVCRESTResponse; +begin + lRes := RESTClient.Get('/sqids/itos/' + IntValue.ToString); + Assert.areEqual(200, lRes.StatusCode); + Assert.AreEqual(TMVCSqids.IntToSqid(IntValue), lRes.Content.Trim, '(local)'); + Assert.AreEqual(Sqid, lRes.Content.Trim, '(remote)'); +end; + procedure TServerTest.TestStringDictionary; var lRes: IMVCRESTResponse; @@ -3024,6 +3055,14 @@ begin Assert.areEqual(HTTP_STATUS.BadRequest, lRes.StatusCode); end; +procedure TServerTest.TestWrongSqid; +var + lRes: IMVCRESTResponse; +begin + lRes := RESTClient.Get('/sqids/itos/123456789123456789123456789123456789'); + Assert.areEqual(400, lRes.StatusCode); +end; + procedure TServerTest.TestTypedDateTimeTypes; var res: IMVCRESTResponse; diff --git a/unittests/general/TestServer/TestServer.dpr b/unittests/general/TestServer/TestServer.dpr index 1bd64a31..854a13a5 100644 --- a/unittests/general/TestServer/TestServer.dpr +++ b/unittests/general/TestServer/TestServer.dpr @@ -86,6 +86,8 @@ begin ReportMemoryLeaksOnShutdown := True; gLocalTimeStampAsUTC := False; UseConsoleLogger := False; + TMVCSqids.SQIDS_ALPHABET := dotEnv.Env('dmvc.sqids.alphabet', 'axDlw8dRnsPCrbZIAEMFG4TQ6gc3iWtOy9v5NBz0LfSmuKV71JHkUhYpej2Xqo'); + TMVCSqids.SQIDS_MIN_LENGTH := dotEnv.Env('dmvc.sqids.min_length', 6); try if WebRequestHandler <> nil then WebRequestHandler.WebModuleClass := WebModuleClass; diff --git a/unittests/general/TestServer/TestServer.dproj b/unittests/general/TestServer/TestServer.dproj index 9e124999..e6dd12bc 100644 --- a/unittests/general/TestServer/TestServer.dproj +++ b/unittests/general/TestServer/TestServer.dproj @@ -9,6 +9,7 @@ None 20.1 Win64 + TestServer true @@ -809,6 +810,9 @@ 1 + + 1 + @@ -1070,6 +1074,7 @@ + 12 diff --git a/unittests/general/TestServer/TestServerControllerU.pas b/unittests/general/TestServer/TestServerControllerU.pas index 56023362..423d118a 100644 --- a/unittests/general/TestServer/TestServerControllerU.pas +++ b/unittests/general/TestServer/TestServerControllerU.pas @@ -373,6 +373,21 @@ type [MVCHTTPMethod([httpGET])] [MVCPath('/issues/542')] procedure TestIssue542; + + {sqids} + [MVCHTTPMethod([httpGET])] + [MVCPath('/sqids/stoi/($id:sqid)')] + function TestReceiveSqidAsInteger(id: Int64): Int64; + + [MVCHTTPMethod([httpGET])] + [MVCPath('/sqids/itos/($id)')] + function TestReceiveIntegerAndReturnSqid(id: Int64): String; + + {invalid converter} + [MVCHTTPMethod([httpGET])] + [MVCPath('/wrongconverter/($id:blablabla)')] + function TestInvalidConverter(id: Int64): Int64; + end; [MVCPath('/private')] @@ -1045,6 +1060,11 @@ begin Render('hello world'); end; +function TTestServerController.TestInvalidConverter(id: Int64): Int64; +begin + Result := id; //never called +end; + procedure TTestServerController.TestIssue406; begin Render(HTTP_STATUS.UnprocessableEntity, TMVCErrorResponseItem.Create('The Message')); @@ -1129,6 +1149,18 @@ begin Render(Person); end; +function TTestServerController.TestReceiveIntegerAndReturnSqid( + id: Int64): String; +begin + Result := TMVCSqids.IntToSqid(id); +end; + +function TTestServerController.TestReceiveSqidAsInteger( + id: Int64): Int64; +begin + Result := id; +end; + procedure TTestServerController.TestRenderStreamAndFreeWithOwnerFalse; var LStream: TMemoryStream;